1 /* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.util; 27 28 import java.security.CryptoPrimitive; 29 import java.security.AlgorithmParameters; 30 import java.security.Key; 31 import java.security.cert.CertPathValidatorException; 32 import java.security.cert.CertPathValidatorException.BasicReason; 33 import java.security.cert.X509Certificate; 34 import java.text.SimpleDateFormat; 35 import java.util.Calendar; 36 import java.util.Date; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Locale; 40 import java.util.Map; 41 import java.util.Set; 42 import java.util.TimeZone; 43 import java.util.regex.Pattern; 44 import java.util.regex.Matcher; 45 46 /** 47 * Algorithm constraints for disabled algorithms property 48 * 49 * See the "jdk.certpath.disabledAlgorithms" specification in java.security 50 * for the syntax of the disabled algorithm string. 51 */ 52 public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints { 53 private static final Debug debug = Debug.getInstance("certpath"); 54 55 // the known security property, jdk.certpath.disabledAlgorithms 56 public static final String PROPERTY_CERTPATH_DISABLED_ALGS = 57 "jdk.certpath.disabledAlgorithms"; 58 59 // the known security property, jdk.tls.disabledAlgorithms 60 public static final String PROPERTY_TLS_DISABLED_ALGS = 61 "jdk.tls.disabledAlgorithms"; 62 63 private final String[] disabledAlgorithms; 64 private final Constraints algorithmConstraints; 65 66 /** 67 * Initialize algorithm constraints with the specified security property. 68 * 69 * @param propertyName the security property name that define the disabled 70 * algorithm constraints 71 */ 72 public DisabledAlgorithmConstraints(String propertyName) { 73 this(propertyName, new AlgorithmDecomposer()); 74 } 75 76 public DisabledAlgorithmConstraints(String propertyName, 77 AlgorithmDecomposer decomposer) { 78 super(decomposer); 79 disabledAlgorithms = getAlgorithms(propertyName); 80 algorithmConstraints = new Constraints(disabledAlgorithms); 81 } 82 83 /* 84 * This only checks if the algorithm has been completely disabled. If 85 * there are keysize or other limit, this method allow the algorithm. 86 */ 87 @Override 88 public final boolean permits(Set<CryptoPrimitive> primitives, 89 String algorithm, AlgorithmParameters parameters) { 90 91 if (primitives == null || primitives.isEmpty()) { 92 throw new IllegalArgumentException( 93 "No cryptographic primitive specified"); 94 } 95 96 return checkAlgorithm(disabledAlgorithms, algorithm, decomposer); 97 } 98 99 /* 100 * Checks if the key algorithm has been disabled or constraints have been 101 * placed on the key. 102 */ 103 @Override 104 public final boolean permits(Set<CryptoPrimitive> primitives, Key key) { 105 return checkConstraints(primitives, "", key, null); 106 } 107 108 /* 109 * Checks if the key algorithm has been disabled or if constraints have 110 * been placed on the key. 111 */ 112 @Override 113 public final boolean permits(Set<CryptoPrimitive> primitives, 114 String algorithm, Key key, AlgorithmParameters parameters) { 115 116 if (algorithm == null || algorithm.length() == 0) { 117 throw new IllegalArgumentException("No algorithm name specified"); 118 } 119 120 return checkConstraints(primitives, algorithm, key, parameters); 121 } 122 123 /* 124 * Check if a x509Certificate object is permitted. Check if all 125 * algorithms are allowed, certificate constraints, and the 126 * public key against key constraints. 127 * 128 * Uses new style permit() which throws exceptions. 129 */ 130 public final void permits(Set<CryptoPrimitive> primitives, 131 CertConstraintParameters cp) throws CertPathValidatorException { 132 checkConstraints(primitives, cp); 133 } 134 135 /* 136 * Check if Certificate object is within the constraints. 137 * Uses new style permit() which throws exceptions. 138 */ 139 public final void permits(Set<CryptoPrimitive> primitives, 140 X509Certificate cert) throws CertPathValidatorException { 141 checkConstraints(primitives, new CertConstraintParameters(cert)); 142 } 143 144 // Check if a string is contained inside the property 145 public boolean checkProperty(String param) { 146 param = param.toLowerCase(Locale.ENGLISH); 147 for (String block : disabledAlgorithms) { 148 if (block.toLowerCase(Locale.ENGLISH).indexOf(param) >= 0) { 149 return true; 150 } 151 } 152 return false; 153 } 154 155 // Check algorithm constraints with key and algorithm 156 private boolean checkConstraints(Set<CryptoPrimitive> primitives, 157 String algorithm, Key key, AlgorithmParameters parameters) { 158 159 // check the key parameter, it cannot be null. 160 if (key == null) { 161 throw new IllegalArgumentException("The key cannot be null"); 162 } 163 164 // check the signature algorithm 165 if (algorithm != null && algorithm.length() != 0) { 166 if (!permits(primitives, algorithm, parameters)) { 167 return false; 168 } 169 } 170 171 // check the key algorithm 172 if (!permits(primitives, key.getAlgorithm(), null)) { 173 return false; 174 } 175 176 // check the key constraints 177 return algorithmConstraints.permits(key); 178 } 179 180 /* 181 * Check algorithm constraints with Certificate 182 * Uses new style permit() which throws exceptions. 183 */ 184 private void checkConstraints(Set<CryptoPrimitive> primitives, 185 CertConstraintParameters cp) throws CertPathValidatorException { 186 187 X509Certificate cert = cp.getCertificate(); 188 String algorithm = cert.getSigAlgName(); 189 190 // Check signature algorithm is not disabled 191 if (!permits(primitives, algorithm, null)) { 192 throw new CertPathValidatorException( 193 "Algorithm constraints check failed on disabled "+ 194 "signature algorithm: " + algorithm, 195 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED); 196 } 197 198 // Check key algorithm is not disabled 199 if (!permits(primitives, cert.getPublicKey().getAlgorithm(), null)) { 200 throw new CertPathValidatorException( 201 "Algorithm constraints check failed on disabled "+ 202 "public key algorithm: " + algorithm, 203 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED); 204 } 205 206 // Check the certificate and key constraints 207 algorithmConstraints.permits(cp); 208 209 } 210 211 /** 212 * Key and Certificate Constraints 213 * 214 * The complete disabling of an algorithm is not handled by Constraints or 215 * Constraint classes. That is addressed with 216 * permit(Set<CryptoPrimitive>, String, AlgorithmParameters) 217 * 218 * When passing a Key to permit(), the boolean return values follow the 219 * same as the interface class AlgorithmConstraints.permit(). This is to 220 * maintain compatibility: 221 * 'true' means the operation is allowed. 222 * 'false' means it failed the constraints and is disallowed. 223 * 224 * When passing CertConstraintParameters through permit(), an exception 225 * will be thrown on a failure to better identify why the operation was 226 * disallowed. 227 */ 228 229 private static class Constraints { 230 private Map<String, Set<Constraint>> constraintsMap = new HashMap<>(); 231 private static final Pattern keySizePattern = Pattern.compile( 232 "keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)"); 233 private static final Pattern denyAfterPattern = Pattern.compile( 234 "denyAfter\\s+(\\d{4})-(\\d{2})-(\\d{2})"); 235 236 public Constraints(String[] constraintArray) { 237 for (String constraintEntry : constraintArray) { 238 if (constraintEntry == null || constraintEntry.isEmpty()) { 239 continue; 240 } 241 242 constraintEntry = constraintEntry.trim(); 243 if (debug != null) { 244 debug.println("Constraints: " + constraintEntry); 245 } 246 247 // Check if constraint is a complete disabling of an 248 // algorithm or has conditions. 249 String algorithm; 250 String policy; 251 int space = constraintEntry.indexOf(' '); 252 if (space > 0) { 253 algorithm = AlgorithmDecomposer.hashName( 254 constraintEntry.substring(0, space). 255 toUpperCase(Locale.ENGLISH)); 256 policy = constraintEntry.substring(space + 1); 257 } else { 258 constraintsMap.computeIfAbsent( 259 constraintEntry.toUpperCase(Locale.ENGLISH), 260 k -> new HashSet<>()); 261 continue; 262 } 263 264 // Convert constraint conditions into Constraint classes 265 Constraint c, lastConstraint = null; 266 // Allow only one jdkCA entry per constraint entry 267 boolean jdkCALimit = false; 268 // Allow only one denyAfter entry per constraint entry 269 boolean denyAfterLimit = false; 270 271 for (String entry : policy.split("&")) { 272 entry = entry.trim(); 273 274 Matcher matcher = keySizePattern.matcher(entry); 275 if (matcher.matches()) { 276 if (debug != null) { 277 debug.println("Constraints set to keySize: " + 278 entry); 279 } 280 c = new KeySizeConstraint(algorithm, 281 KeySizeConstraint.Operator.of(matcher.group(1)), 282 Integer.parseInt(matcher.group(2))); 283 284 } else if (entry.equalsIgnoreCase("jdkCA")) { 285 if (debug != null) { 286 debug.println("Constraints set to jdkCA."); 287 } 288 if (jdkCALimit) { 289 throw new IllegalArgumentException("Only one " + 290 "jdkCA entry allowed in property. " + 291 "Constraint: " + constraintEntry); 292 } 293 c = new jdkCAConstraint(algorithm); 294 jdkCALimit = true; 295 296 } else if(matcher.usePattern(denyAfterPattern).matches()) { 297 if (debug != null) { 298 debug.println("Constraints set to denyAfter"); 299 } 300 if (denyAfterLimit) { 301 throw new IllegalArgumentException("Only one " + 302 "denyAfter entry allowed in property. " + 303 "Constraint: " + constraintEntry); 304 } 305 int year = Integer.parseInt(matcher.group(1)); 306 int month = Integer.parseInt(matcher.group(2)); 307 int day = Integer.parseInt(matcher.group(3)); 308 c = new DenyAfterConstraint(algorithm, year, month, 309 day); 310 denyAfterLimit = true; 311 } else { 312 throw new IllegalArgumentException("Error in security" + 313 " property. Constraint unknown: " + entry); 314 } 315 316 // Link multiple conditions for a single constraint 317 // into a linked list. 318 if (lastConstraint == null) { 319 if (!constraintsMap.containsKey(algorithm)) { 320 constraintsMap.putIfAbsent(algorithm, 321 new HashSet<>()); 322 } 323 constraintsMap.get(algorithm).add(c); 324 } else { 325 lastConstraint.nextConstraint = c; 326 } 327 lastConstraint = c; 328 } 329 } 330 } 331 332 // Get applicable constraints based off the signature algorithm 333 private Set<Constraint> getConstraints(String algorithm) { 334 return constraintsMap.get(algorithm); 335 } 336 337 // Check if KeySizeConstraints permit the specified key 338 public boolean permits(Key key) { 339 Set<Constraint> set = getConstraints(key.getAlgorithm()); 340 if (set == null) { 341 return true; 342 } 343 for (Constraint constraint : set) { 344 if (!constraint.permits(key)) { 345 if (debug != null) { 346 debug.println("keySizeConstraint: failed key " + 347 "constraint check " + KeyUtil.getKeySize(key)); 348 } 349 return false; 350 } 351 } 352 return true; 353 } 354 355 // Check if constraints permit this cert. 356 public void permits(CertConstraintParameters cp) 357 throws CertPathValidatorException { 358 X509Certificate cert = cp.getCertificate(); 359 360 if (debug != null) { 361 debug.println("Constraints.permits(): " + cert.getSigAlgName()); 362 } 363 364 // Get all signature algorithms to check for constraints 365 Set<String> algorithms = 366 AlgorithmDecomposer.decomposeOneHash(cert.getSigAlgName()); 367 if (algorithms == null || algorithms.isEmpty()) { 368 return; 369 } 370 371 // Attempt to add the public key algorithm to the set 372 algorithms.add(cert.getPublicKey().getAlgorithm()); 373 374 // Check all applicable constraints 375 for (String algorithm : algorithms) { 376 Set<Constraint> set = getConstraints(algorithm); 377 if (set == null) { 378 continue; 379 } 380 for (Constraint constraint : set) { 381 constraint.permits(cp); 382 } 383 } 384 } 385 } 386 387 /** 388 * This abstract Constraint class for algorithm-based checking 389 * may contain one or more constraints. If the '&' on the {@Security} 390 * property is used, multiple constraints have been grouped together 391 * requiring all the constraints to fail for the check to be disallowed. 392 * 393 * If the class contains multiple constraints, the next constraint 394 * is stored in {@code nextConstraint} in linked-list fashion. 395 */ 396 private abstract static class Constraint { 397 String algorithm; 398 Constraint nextConstraint = null; 399 400 // operator 401 enum Operator { 402 EQ, // "==" 403 NE, // "!=" 404 LT, // "<" 405 LE, // "<=" 406 GT, // ">" 407 GE; // ">=" 408 409 static Operator of(String s) { 410 switch (s) { 411 case "==": 412 return EQ; 413 case "!=": 414 return NE; 415 case "<": 416 return LT; 417 case "<=": 418 return LE; 419 case ">": 420 return GT; 421 case ">=": 422 return GE; 423 } 424 425 throw new IllegalArgumentException("Error in security " + 426 "property. " + s + " is not a legal Operator"); 427 } 428 } 429 430 /** 431 * Check if an algorithm constraint is permitted with a given key. 432 * 433 * If the check inside of {@code permit()} fails, it must call 434 * {@code next()} with the same {@code Key} parameter passed if 435 * multiple constraints need to be checked. 436 * 437 * @param key Public key 438 * @return 'true' if constraint is allowed, 'false' if disallowed. 439 */ 440 public boolean permits(Key key) { 441 return true; 442 } 443 444 /** 445 * Check if an algorithm constraint is permitted with a given 446 * CertConstraintParameters. 447 * 448 * If the check inside of {@code permits()} fails, it must call 449 * {@code next()} with the same {@code CertConstraintParameters} 450 * parameter passed if multiple constraints need to be checked. 451 * 452 * @param cp CertConstraintParameter containing certificate info 453 * @throws CertPathValidatorException if constraint disallows. 454 * 455 */ 456 public abstract void permits(CertConstraintParameters cp) 457 throws CertPathValidatorException; 458 459 /** 460 * Recursively check if the constraints are allowed. 461 * 462 * If {@code nextConstraint} is non-null, this method will 463 * call {@code nextConstraint}'s {@code permits()} to check if the 464 * constraint is allowed or denied. If the constraint's 465 * {@code permits()} is allowed, this method will exit this and any 466 * recursive next() calls, returning 'true'. If the constraints called 467 * were disallowed, the last constraint will throw 468 * {@code CertPathValidatorException}. 469 * 470 * @param cp CertConstraintParameters 471 * @return 'true' if constraint allows the operation, 'false' if 472 * we are at the end of the constraint list or, 473 * {@code nextConstraint} is null. 474 */ 475 boolean next(CertConstraintParameters cp) 476 throws CertPathValidatorException { 477 if (nextConstraint != null) { 478 nextConstraint.permits(cp); 479 return true; 480 } 481 return false; 482 } 483 484 /** 485 * Recursively check if this constraint is allowed, 486 * 487 * If {@code nextConstraint} is non-null, this method will 488 * call {@code nextConstraint}'s {@code permit()} to check if the 489 * constraint is allowed or denied. If the constraint's 490 * {@code permit()} is allowed, this method will exit this and any 491 * recursive next() calls, returning 'true'. If the constraints 492 * called were disallowed the check will exit with 'false'. 493 * 494 * @param key Public key 495 * @return 'true' if constraint allows the operation, 'false' if 496 * the constraint denies the operation. 497 */ 498 boolean next(Key key) { 499 if (nextConstraint != null && nextConstraint.permits(key)) { 500 return true; 501 } 502 return false; 503 } 504 } 505 506 /* 507 * This class contains constraints dealing with the certificate chain 508 * of the certificate. 509 */ 510 private static class jdkCAConstraint extends Constraint { 511 jdkCAConstraint(String algo) { 512 algorithm = algo; 513 } 514 515 /* 516 * Check if CertConstraintParameters has a trusted match, if it does 517 * call next() for any following constraints. If it does not, exit 518 * as this constraint(s) does not restrict the operation. 519 */ 520 public void permits(CertConstraintParameters cp) 521 throws CertPathValidatorException { 522 if (debug != null) { 523 debug.println("jdkCAConstraints.permits(): " + algorithm); 524 } 525 526 // Check chain has a trust anchor in cacerts 527 if (cp.isTrustedMatch()) { 528 if (next(cp)) { 529 return; 530 } 531 throw new CertPathValidatorException( 532 "Algorithm constraints check failed on certificate " + 533 "anchor limits. " + algorithm + " used with " + 534 cp.getCertificate().getSubjectX500Principal(), 535 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED); 536 } 537 } 538 } 539 540 /* 541 * This class handles the denyAfter constraint. The date is in the UTC/GMT 542 * timezone. 543 */ 544 private static class DenyAfterConstraint extends Constraint { 545 private Date denyAfterDate; 546 private static final SimpleDateFormat dateFormat = 547 new SimpleDateFormat("EEE, MMM d HH:mm:ss z yyyy"); 548 549 DenyAfterConstraint(String algo, int year, int month, int day) { 550 Calendar c; 551 552 algorithm = algo; 553 554 if (debug != null) { 555 debug.println("DenyAfterConstraint read in as: year " + 556 year + ", month = " + month + ", day = " + day); 557 } 558 559 c = new Calendar.Builder().setTimeZone(TimeZone.getTimeZone("GMT")) 560 .setDate(year, month - 1, day).build(); 561 562 if (year > c.getActualMaximum(Calendar.YEAR) || 563 year < c.getActualMinimum(Calendar.YEAR)) { 564 throw new IllegalArgumentException( 565 "Invalid year given in constraint: " + year); 566 } 567 if ((month - 1) > c.getActualMaximum(Calendar.MONTH) || 568 (month - 1) < c.getActualMinimum(Calendar.MONTH)) { 569 throw new IllegalArgumentException( 570 "Invalid month given in constraint: " + month); 571 } 572 if (day > c.getActualMaximum(Calendar.DAY_OF_MONTH) || 573 day < c.getActualMinimum(Calendar.DAY_OF_MONTH)) { 574 throw new IllegalArgumentException( 575 "Invalid Day of Month given in constraint: " + day); 576 } 577 578 denyAfterDate = c.getTime(); 579 if (debug != null) { 580 debug.println("DenyAfterConstraint date set to: " + 581 dateFormat.format(denyAfterDate)); 582 } 583 } 584 585 /* 586 * Checking that the provided date is not beyond the constraint date. 587 * The provided date can be the PKIXParameter date if given, 588 * otherwise it is the current date. 589 * 590 * If the constraint disallows, call next() for any following 591 * constraints. Throw an exception if this is the last constraint. 592 */ 593 @Override 594 public void permits(CertConstraintParameters cp) 595 throws CertPathValidatorException { 596 Date currentDate; 597 String errmsg; 598 599 if (cp.getJARTimestamp() != null) { 600 currentDate = cp.getJARTimestamp().getTimestamp(); 601 errmsg = "JAR Timestamp date: "; 602 } else if (cp.getPKIXParamDate() != null) { 603 currentDate = cp.getPKIXParamDate(); 604 errmsg = "PKIXParameter date: "; 605 } else { 606 currentDate = new Date(); 607 errmsg = "Certificate date: "; 608 } 609 610 if (!denyAfterDate.after(currentDate)) { 611 if (next(cp)) { 612 return; 613 } 614 throw new CertPathValidatorException( 615 "denyAfter constraint check failed: " + algorithm + 616 " used with Constraint date: " + 617 dateFormat.format(denyAfterDate) + "; " 618 + errmsg + dateFormat.format(currentDate), 619 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED); 620 } 621 } 622 623 /* 624 * Return result if the constraint's date is beyond the current date 625 * in UTC timezone. 626 */ 627 public boolean permits(Key key) { 628 if (next(key)) { 629 return true; 630 } 631 if (debug != null) { 632 debug.println("DenyAfterConstraints.permits(): " + algorithm); 633 } 634 635 return denyAfterDate.after(new Date()); 636 } 637 } 638 639 /* 640 * This class contains constraints dealing with the key size 641 * support limits per algorithm. e.g. "keySize <= 1024" 642 */ 643 private static class KeySizeConstraint extends Constraint { 644 645 private int minSize; // the minimal available key size 646 private int maxSize; // the maximal available key size 647 private int prohibitedSize = -1; // unavailable key sizes 648 private int size; 649 650 public KeySizeConstraint(String algo, Operator operator, int length) { 651 algorithm = algo; 652 switch (operator) { 653 case EQ: // an unavailable key size 654 this.minSize = 0; 655 this.maxSize = Integer.MAX_VALUE; 656 prohibitedSize = length; 657 break; 658 case NE: 659 this.minSize = length; 660 this.maxSize = length; 661 break; 662 case LT: 663 this.minSize = length; 664 this.maxSize = Integer.MAX_VALUE; 665 break; 666 case LE: 667 this.minSize = length + 1; 668 this.maxSize = Integer.MAX_VALUE; 669 break; 670 case GT: 671 this.minSize = 0; 672 this.maxSize = length; 673 break; 674 case GE: 675 this.minSize = 0; 676 this.maxSize = length > 1 ? (length - 1) : 0; 677 break; 678 default: 679 // unlikely to happen 680 this.minSize = Integer.MAX_VALUE; 681 this.maxSize = -1; 682 } 683 } 684 685 /* 686 * If we are passed a certificate, extract the public key and use it. 687 * 688 * Check if each constraint fails and check if there is a linked 689 * constraint Any permitted constraint will exit the linked list 690 * to allow the operation. 691 */ 692 public void permits(CertConstraintParameters cp) 693 throws CertPathValidatorException { 694 if (!permitsImpl(cp.getCertificate().getPublicKey())) { 695 if (nextConstraint != null) { 696 nextConstraint.permits(cp); 697 return; 698 } 699 throw new CertPathValidatorException( 700 "Algorithm constraints check failed on keysize limits. " 701 + algorithm + " " + size + "bit key used with " 702 + cp.getCertificate().getSubjectX500Principal(), 703 null, null, -1, BasicReason.ALGORITHM_CONSTRAINED); 704 } 705 } 706 707 708 // Check if key constraint disable the specified key 709 // Uses old style permit() 710 public boolean permits(Key key) { 711 // If we recursively find a constraint that permits us to use 712 // this key, return true and skip any other constraint checks. 713 if (nextConstraint != null && nextConstraint.permits(key)) { 714 return true; 715 } 716 if (debug != null) { 717 debug.println("KeySizeConstraints.permits(): " + algorithm); 718 } 719 720 return permitsImpl(key); 721 } 722 723 private boolean permitsImpl(Key key) { 724 // Verify this constraint is for this public key algorithm 725 if (algorithm.compareToIgnoreCase(key.getAlgorithm()) != 0) { 726 return true; 727 } 728 729 size = KeyUtil.getKeySize(key); 730 if (size == 0) { 731 return false; // we don't allow any key of size 0. 732 } else if (size > 0) { 733 return !((size < minSize) || (size > maxSize) || 734 (prohibitedSize == size)); 735 } // Otherwise, the key size is not accessible. Conservatively, 736 // please don't disable such keys. 737 738 return true; 739 } 740 } 741 } 742