1 package org.bouncycastle.jsse.provider;
2 
3 import java.security.AlgorithmParameters;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.LinkedHashMap;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.TreeMap;
12 import java.util.Vector;
13 import java.util.logging.Logger;
14 
15 import org.bouncycastle.jsse.java.security.BCAlgorithmConstraints;
16 import org.bouncycastle.jsse.java.security.BCCryptoPrimitive;
17 import org.bouncycastle.tls.NamedGroup;
18 import org.bouncycastle.tls.ProtocolVersion;
19 import org.bouncycastle.tls.TlsUtils;
20 import org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCrypto;
21 import org.bouncycastle.util.Arrays;
22 import org.bouncycastle.util.Integers;
23 
24 class NamedGroupInfo
25 {
26     private static final Logger LOG = Logger.getLogger(NamedGroupInfo.class.getName());
27 
28     private static final String PROPERTY_NAMED_GROUPS = "jdk.tls.namedGroups";
29 
30     // NOTE: Not all of these are necessarily enabled/supported; it will be checked at runtime
31     private enum All
32     {
33         sect163k1(NamedGroup.sect163k1, "EC"),
34         sect163r1(NamedGroup.sect163r1, "EC"),
35         sect163r2(NamedGroup.sect163r2, "EC"),
36         sect193r1(NamedGroup.sect193r1, "EC"),
37         sect193r2(NamedGroup.sect193r2, "EC"),
38         sect233k1(NamedGroup.sect233k1, "EC"),
39         sect233r1(NamedGroup.sect233r1, "EC"),
40         sect239k1(NamedGroup.sect239k1, "EC"),
41         sect283k1(NamedGroup.sect283k1, "EC"),
42         sect283r1(NamedGroup.sect283r1, "EC"),
43         sect409k1(NamedGroup.sect409k1, "EC"),
44         sect409r1(NamedGroup.sect409r1, "EC"),
45         sect571k1(NamedGroup.sect571k1, "EC"),
46         sect571r1(NamedGroup.sect571r1, "EC"),
47         secp160k1(NamedGroup.secp160k1, "EC"),
48         secp160r1(NamedGroup.secp160r1, "EC"),
49         secp160r2(NamedGroup.secp160r2, "EC"),
50         secp192k1(NamedGroup.secp192k1, "EC"),
51         secp192r1(NamedGroup.secp192r1, "EC"),
52         secp224k1(NamedGroup.secp224k1, "EC"),
53         secp224r1(NamedGroup.secp224r1, "EC"),
54         secp256k1(NamedGroup.secp256k1, "EC"),
55         secp256r1(NamedGroup.secp256r1, "EC"),
56         secp384r1(NamedGroup.secp384r1, "EC"),
57         secp521r1(NamedGroup.secp521r1, "EC"),
58 
59         brainpoolP256r1(NamedGroup.brainpoolP256r1, "EC"),
60         brainpoolP384r1(NamedGroup.brainpoolP384r1, "EC"),
61         brainpoolP512r1(NamedGroup.brainpoolP512r1, "EC"),
62 
63         x25519(NamedGroup.x25519, "XDH"),
64         x448(NamedGroup.x448, "XDH"),
65 
66         brainpoolP256r1tls13(NamedGroup.brainpoolP256r1tls13, "EC"),
67         brainpoolP384r1tls13(NamedGroup.brainpoolP384r1tls13, "EC"),
68         brainpoolP512r1tls13(NamedGroup.brainpoolP512r1tls13, "EC"),
69 
70         curveSM2(NamedGroup.curveSM2, "EC"),
71 
72         ffdhe2048(NamedGroup.ffdhe2048, "DiffieHellman"),
73         ffdhe3072(NamedGroup.ffdhe3072, "DiffieHellman"),
74         ffdhe4096(NamedGroup.ffdhe4096, "DiffieHellman"),
75         ffdhe6144(NamedGroup.ffdhe6144, "DiffieHellman"),
76         ffdhe8192(NamedGroup.ffdhe8192, "DiffieHellman");
77 
78         private final int namedGroup;
79         private final String name;
80         private final String text;
81         private final String jcaAlgorithm;
82         private final String jcaGroup;
83         private final boolean char2;
84         private final boolean supportedPost13;
85         private final boolean supportedPre13;
86         private final int bitsECDH;
87         private final int bitsFFDHE;
88 
All(int namedGroup, String jcaAlgorithm)89         private All(int namedGroup, String jcaAlgorithm)
90         {
91             this.namedGroup = namedGroup;
92             this.name = NamedGroup.getName(namedGroup);
93             this.text = NamedGroup.getText(namedGroup);
94             this.jcaAlgorithm = jcaAlgorithm;
95             this.jcaGroup = NamedGroup.getStandardName(namedGroup);
96             this.supportedPost13 = NamedGroup.canBeNegotiated(namedGroup, ProtocolVersion.TLSv13);
97             this.supportedPre13 = NamedGroup.canBeNegotiated(namedGroup, ProtocolVersion.TLSv12);
98             this.char2 = NamedGroup.isChar2Curve(namedGroup);
99             this.bitsECDH = NamedGroup.getCurveBits(namedGroup);
100             this.bitsFFDHE = NamedGroup.getFiniteFieldBits(namedGroup);
101         }
102     }
103 
104     private static final int[] CANDIDATES_DEFAULT = {
105         NamedGroup.x25519,
106         NamedGroup.x448,
107         NamedGroup.secp256r1,
108         NamedGroup.secp384r1,
109         NamedGroup.secp521r1,
110         NamedGroup.brainpoolP256r1tls13,
111         NamedGroup.brainpoolP384r1tls13,
112         NamedGroup.brainpoolP512r1tls13,
113         NamedGroup.ffdhe2048,
114         NamedGroup.ffdhe3072,
115         NamedGroup.ffdhe4096,
116     };
117 
118     static class PerConnection
119     {
120         // NOTE: Should have predictable iteration order (by preference)
121         private final Map<Integer, NamedGroupInfo> local;
122         private final boolean localECDSA;
123 
124         private List<NamedGroupInfo> peer;
125 
PerConnection(Map<Integer, NamedGroupInfo> local, boolean localECDSA)126         PerConnection(Map<Integer, NamedGroupInfo> local, boolean localECDSA)
127         {
128             this.local = local;
129             this.localECDSA = localECDSA;
130 
131             this.peer = null;
132         }
133 
getPeer()134         public synchronized List<NamedGroupInfo> getPeer()
135         {
136             return peer;
137         }
138 
setPeer(List<NamedGroupInfo> peer)139         private synchronized void setPeer(List<NamedGroupInfo> peer)
140         {
141             this.peer = peer;
142         }
143     }
144 
145     static class PerContext
146     {
147         private final Map<Integer, NamedGroupInfo> index;
148         private final int[] candidates;
149 
PerContext(Map<Integer, NamedGroupInfo> index, int[] candidates)150         PerContext(Map<Integer, NamedGroupInfo> index, int[] candidates)
151         {
152             this.index = index;
153             this.candidates = candidates;
154         }
155     }
156 
createPerConnection(PerContext perContext, ProvSSLParameters sslParameters, ProtocolVersion[] activeProtocolVersions)157     static PerConnection createPerConnection(PerContext perContext, ProvSSLParameters sslParameters, ProtocolVersion[] activeProtocolVersions)
158     {
159         Map<Integer, NamedGroupInfo> local = createLocal(perContext, sslParameters, activeProtocolVersions);
160         boolean localECDSA = createLocalECDSA(local);
161 
162         return new PerConnection(local, localECDSA);
163     }
164 
createPerContext(boolean isFipsContext, JcaTlsCrypto crypto)165     static PerContext createPerContext(boolean isFipsContext, JcaTlsCrypto crypto)
166     {
167         Map<Integer, NamedGroupInfo> index = createIndex(isFipsContext, crypto);
168         int[] candidates = createCandidates(index);
169 
170         return new PerContext(index, candidates);
171     }
172 
getMaximumBitsServerECDH(PerConnection perConnection)173     static int getMaximumBitsServerECDH(PerConnection perConnection)
174     {
175         int maxBits = 0;
176         for (NamedGroupInfo namedGroupInfo : getEffectivePeer(perConnection))
177         {
178             maxBits = Math.max(maxBits, namedGroupInfo.getBitsECDH());
179         }
180         return maxBits;
181     }
182 
getMaximumBitsServerFFDHE(PerConnection perConnection)183     static int getMaximumBitsServerFFDHE(PerConnection perConnection)
184     {
185         int maxBits = 0;
186         for (NamedGroupInfo namedGroupInfo : getEffectivePeer(perConnection))
187         {
188             maxBits = Math.max(maxBits, namedGroupInfo.getBitsFFDHE());
189         }
190         return maxBits;
191     }
192 
getNamedGroup(PerContext perContext, int namedGroup)193     static NamedGroupInfo getNamedGroup(PerContext perContext, int namedGroup)
194     {
195         return perContext.index.get(namedGroup);
196     }
197 
getSupportedGroupsLocalClient(PerConnection perConnection)198     static Vector<Integer> getSupportedGroupsLocalClient(PerConnection perConnection)
199     {
200         return new Vector<Integer>(perConnection.local.keySet());
201     }
202 
getSupportedGroupsLocalServer(PerConnection perConnection)203     static int[] getSupportedGroupsLocalServer(PerConnection perConnection)
204     {
205         Set<Integer> keys = perConnection.local.keySet();
206         int count = keys.size(), pos = 0;
207         int[] result = new int[count];
208         for (Integer key : keys)
209         {
210             result[pos++] = key.intValue();
211         }
212         return result;
213     }
214 
hasAnyECDSALocal(PerConnection perConnection)215     static boolean hasAnyECDSALocal(PerConnection perConnection)
216     {
217         return perConnection.localECDSA;
218     }
219 
hasLocal(PerConnection perConnection, int namedGroup)220     static boolean hasLocal(PerConnection perConnection, int namedGroup)
221     {
222         return perConnection.local.containsKey(namedGroup);
223     }
224 
notifyPeer(PerConnection perConnection, int[] peerNamedGroups)225     static void notifyPeer(PerConnection perConnection, int[] peerNamedGroups)
226     {
227         List<NamedGroupInfo> peer = createPeer(perConnection, peerNamedGroups);
228 
229         perConnection.setPeer(peer);
230     }
231 
selectServerECDH(PerConnection perConnection, int minimumBitsECDH)232     static int selectServerECDH(PerConnection perConnection, int minimumBitsECDH)
233     {
234         for (NamedGroupInfo namedGroupInfo : getEffectivePeer(perConnection))
235         {
236             if (namedGroupInfo.getBitsECDH() >= minimumBitsECDH)
237             {
238                 return namedGroupInfo.getNamedGroup();
239             }
240         }
241         return -1;
242     }
243 
selectServerFFDHE(PerConnection perConnection, int minimumBitsFFDHE)244     static int selectServerFFDHE(PerConnection perConnection, int minimumBitsFFDHE)
245     {
246         for (NamedGroupInfo namedGroupInfo : getEffectivePeer(perConnection))
247         {
248             if (namedGroupInfo.getBitsFFDHE() >= minimumBitsFFDHE)
249             {
250                 return namedGroupInfo.getNamedGroup();
251             }
252         }
253         return -1;
254     }
255 
addNamedGroup(boolean isFipsContext, JcaTlsCrypto crypto, boolean disableChar2, boolean disableFFDHE, Map<Integer, NamedGroupInfo> ng, All all)256     private static void addNamedGroup(boolean isFipsContext, JcaTlsCrypto crypto, boolean disableChar2,
257         boolean disableFFDHE, Map<Integer, NamedGroupInfo> ng, All all)
258     {
259         final int namedGroup = all.namedGroup;
260 
261         if (isFipsContext && !FipsUtils.isFipsNamedGroup(namedGroup))
262         {
263             // In FIPS mode, non-FIPS groups are currently not even entered into the map
264             return;
265         }
266 
267         boolean disable = (disableChar2 && all.char2) || (disableFFDHE && all.bitsFFDHE > 0);
268 
269         boolean enabled = !disable && (null != all.jcaGroup) && crypto.hasNamedGroup(namedGroup);
270 
271         AlgorithmParameters algorithmParameters = null;
272         if (enabled)
273         {
274             // TODO[jsse] Consider also fetching 'jcaAlgorithm'
275             try
276             {
277                 algorithmParameters = crypto.getNamedGroupAlgorithmParameters(namedGroup);
278             }
279             catch (Exception e)
280             {
281                 enabled = false;
282             }
283         }
284 
285         NamedGroupInfo namedGroupInfo = new NamedGroupInfo(all, algorithmParameters, enabled);
286 
287         if (null != ng.put(namedGroup, namedGroupInfo))
288         {
289             throw new IllegalStateException("Duplicate entries for NamedGroupInfo");
290         }
291     }
292 
createCandidates(Map<Integer, NamedGroupInfo> index)293     private static int[] createCandidates(Map<Integer, NamedGroupInfo> index)
294     {
295         String[] names = PropertyUtils.getStringArraySystemProperty(PROPERTY_NAMED_GROUPS);
296         if (null == names)
297         {
298             return CANDIDATES_DEFAULT;
299         }
300 
301         int[] result = new int[names.length];
302         int count = 0;
303         for (String name : names)
304         {
305             int namedGroup = getNamedGroupByName(name);
306             if (namedGroup < 0)
307             {
308                 LOG.warning("'" + PROPERTY_NAMED_GROUPS + "' contains unrecognised NamedGroup: " + name);
309                 continue;
310             }
311 
312             NamedGroupInfo namedGroupInfo = index.get(namedGroup);
313             if (null == namedGroupInfo)
314             {
315                 LOG.warning("'" + PROPERTY_NAMED_GROUPS + "' contains unsupported NamedGroup: " + name);
316                 continue;
317             }
318 
319             if (!namedGroupInfo.isEnabled())
320             {
321                 LOG.warning("'" + PROPERTY_NAMED_GROUPS + "' contains disabled NamedGroup: " + name);
322                 continue;
323             }
324 
325             result[count++] = namedGroup;
326         }
327         if (count < result.length)
328         {
329             result = Arrays.copyOf(result, count);
330         }
331         if (result.length < 1)
332         {
333             LOG.severe("'" + PROPERTY_NAMED_GROUPS + "' contained no usable NamedGroup values");
334         }
335         return result;
336     }
337 
createIndex(boolean isFipsContext, JcaTlsCrypto crypto)338     private static Map<Integer, NamedGroupInfo> createIndex(boolean isFipsContext, JcaTlsCrypto crypto)
339     {
340         Map<Integer, NamedGroupInfo> ng = new TreeMap<Integer, NamedGroupInfo>();
341 
342         final boolean disableChar2 = PropertyUtils.getBooleanSystemProperty("org.bouncycastle.jsse.ec.disableChar2", false)
343                                   || PropertyUtils.getBooleanSystemProperty("org.bouncycastle.ec.disable_f2m", false);
344 
345         final boolean disableFFDHE = !PropertyUtils.getBooleanSystemProperty("jsse.enableFFDHE", true);
346 
347         for (All all : All.values())
348         {
349             addNamedGroup(isFipsContext, crypto, disableChar2, disableFFDHE, ng, all);
350         }
351 
352         return ng;
353     }
354 
createLocal(PerContext perContext, ProvSSLParameters sslParameters, ProtocolVersion[] activeProtocolVersions)355     private static Map<Integer, NamedGroupInfo> createLocal(PerContext perContext,
356         ProvSSLParameters sslParameters, ProtocolVersion[] activeProtocolVersions)
357     {
358         ProtocolVersion latest = ProtocolVersion.getLatestTLS(activeProtocolVersions);
359         ProtocolVersion earliest = ProtocolVersion.getEarliestTLS(activeProtocolVersions);
360 
361         BCAlgorithmConstraints algorithmConstraints = sslParameters.getAlgorithmConstraints();
362         boolean post13Active = TlsUtils.isTLSv13(latest);
363         boolean pre13Active = !TlsUtils.isTLSv13(earliest);
364 
365         int count = perContext.candidates.length;
366         LinkedHashMap<Integer, NamedGroupInfo> result = new LinkedHashMap<Integer, NamedGroupInfo>(count);
367         for (int i = 0; i < count; ++i)
368         {
369             Integer candidate = Integers.valueOf(perContext.candidates[i]);
370             NamedGroupInfo namedGroupInfo = perContext.index.get(candidate);
371 
372             if (null != namedGroupInfo
373                 && namedGroupInfo.isActive(algorithmConstraints, post13Active, pre13Active))
374             {
375                 // NOTE: Re-insertion doesn't affect iteration order for insertion-order LinkedHashMap
376                 result.put(candidate, namedGroupInfo);
377             }
378         }
379         return result;
380     }
381 
createLocalECDSA(Map<Integer, NamedGroupInfo> local)382     private static boolean createLocalECDSA(Map<Integer, NamedGroupInfo> local)
383     {
384         for (NamedGroupInfo namedGroupInfo : local.values())
385         {
386             if (NamedGroup.refersToAnECDSACurve(namedGroupInfo.getNamedGroup()))
387             {
388                 return true;
389             }
390         }
391         return false;
392     }
393 
createPeer(PerConnection perConnection, int[] peerNamedGroups)394     private static List<NamedGroupInfo> createPeer(PerConnection perConnection, int[] peerNamedGroups)
395     {
396         // TODO[jsse] Is there any reason to preserve the unrecognized/disabled groups?
397 
398         return getNamedGroupInfos(perConnection.local, peerNamedGroups);
399     }
400 
getEffectivePeer(PerConnection perConnection)401     private static Collection<NamedGroupInfo> getEffectivePeer(PerConnection perConnection)
402     {
403         List<NamedGroupInfo> peer = perConnection.getPeer();
404         if (!peer.isEmpty())
405         {
406             return peer;
407         }
408 
409         return perConnection.local.values();
410     }
411 
getNamedGroupByName(String name)412     private static int getNamedGroupByName(String name)
413     {
414         for (All all : All.values())
415         {
416             if (all.name.equalsIgnoreCase(name))
417             {
418                 return all.namedGroup;
419             }
420         }
421 
422         return -1;
423     }
424 
getNamedGroupInfos(Map<Integer, NamedGroupInfo> namedGroupInfos, int[] namedGroups)425     private static List<NamedGroupInfo> getNamedGroupInfos(Map<Integer, NamedGroupInfo> namedGroupInfos, int[] namedGroups)
426     {
427         if (TlsUtils.isNullOrEmpty(namedGroups))
428         {
429             return Collections.emptyList();
430         }
431 
432         int count = namedGroups.length;
433         ArrayList<NamedGroupInfo> result = new ArrayList<NamedGroupInfo>(count);
434         for (int i = 0; i < count; ++i)
435         {
436             int namedGroup = namedGroups[i];
437 
438             NamedGroupInfo namedGroupInfo = namedGroupInfos.get(namedGroup);
439             if (null != namedGroupInfo)
440             {
441                 result.add(namedGroupInfo);
442             }
443         }
444         if (result.isEmpty())
445         {
446             return Collections.emptyList();
447         }
448         result.trimToSize();
449         return result;
450     }
451 
452     private final All all;
453     private final AlgorithmParameters algorithmParameters;
454     private final boolean enabled;
455 
NamedGroupInfo(All all, AlgorithmParameters algorithmParameters, boolean enabled)456     NamedGroupInfo(All all, AlgorithmParameters algorithmParameters, boolean enabled)
457     {
458         this.all = all;
459         this.algorithmParameters = algorithmParameters;
460         this.enabled = enabled;
461     }
462 
getBitsECDH()463     int getBitsECDH()
464     {
465         return all.bitsECDH;
466     }
467 
getBitsFFDHE()468     int getBitsFFDHE()
469     {
470         return all.bitsFFDHE;
471     }
472 
getJcaAlgorithm()473     String getJcaAlgorithm()
474     {
475         return all.jcaAlgorithm;
476     }
477 
getJcaGroup()478     String getJcaGroup()
479     {
480         return all.jcaGroup;
481     }
482 
getNamedGroup()483     int getNamedGroup()
484     {
485         return all.namedGroup;
486     }
487 
isActive(BCAlgorithmConstraints algorithmConstraints, boolean post13Active, boolean pre13Active)488     boolean isActive(BCAlgorithmConstraints algorithmConstraints, boolean post13Active, boolean pre13Active)
489     {
490         return enabled
491             && ((post13Active && isSupportedPost13()) || (pre13Active && isSupportedPre13()))
492             && isPermittedBy(algorithmConstraints);
493     }
494 
isEnabled()495     boolean isEnabled()
496     {
497         return enabled;
498     }
499 
isSupportedPost13()500     boolean isSupportedPost13()
501     {
502         return all.supportedPost13;
503     }
504 
isSupportedPre13()505     boolean isSupportedPre13()
506     {
507         return all.supportedPre13;
508     }
509 
510     @Override
toString()511     public String toString()
512     {
513         return all.text;
514     }
515 
isPermittedBy(BCAlgorithmConstraints algorithmConstraints)516     private boolean isPermittedBy(BCAlgorithmConstraints algorithmConstraints)
517     {
518         Set<BCCryptoPrimitive> primitives = JsseUtils.KEY_AGREEMENT_CRYPTO_PRIMITIVES_BC;
519 
520         return algorithmConstraints.permits(primitives, getJcaGroup(), null)
521             && algorithmConstraints.permits(primitives, getJcaAlgorithm(), algorithmParameters);
522     }
523 }
524