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