17b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root/* 27b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * Copyright (C) 2014 The Android Open Source Project 37b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * 47b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * Licensed under the Apache License, Version 2.0 (the "License"); 57b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * you may not use this file except in compliance with the License. 67b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * You may obtain a copy of the License at 77b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * 87b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * http://www.apache.org/licenses/LICENSE-2.0 97b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * 107b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * Unless required by applicable law or agreed to in writing, software 117b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * distributed under the License is distributed on an "AS IS" BASIS, 127b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * See the License for the specific language governing permissions and 147b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * limitations under the License. 157b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root */ 167b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 177b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootpackage org.conscrypt; 187b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 197b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.io.IOException; 207b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.io.NotSerializableException; 217b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.io.ObjectInputStream; 227b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.io.ObjectOutputStream; 237b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.math.BigInteger; 247b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.security.InvalidKeyException; 257b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport java.security.spec.InvalidKeySpecException; 267b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport javax.crypto.interfaces.DHPrivateKey; 277b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport javax.crypto.spec.DHParameterSpec; 287b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootimport javax.crypto.spec.DHPrivateKeySpec; 297b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 307b27ca77c328e510a165712a497c20b67c68e8a3Kenny Rootpublic class OpenSSLDHPrivateKey implements DHPrivateKey, OpenSSLKeyHolder { 317b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private static final long serialVersionUID = -7321023036951606638L; 327b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 337b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private transient OpenSSLKey key; 347b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 357b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root /** base prime */ 367b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private transient byte[] p; 377b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 387b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root /** generator */ 397b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private transient byte[] g; 407b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 417b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root /** private key */ 427b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private transient byte[] x; 437b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 44e526e75cfe12c2908d37b03562ac48a5bbefdf11Kenny Root private transient Object mParamsLock = new Object(); 457b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 467b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private transient boolean readParams; 477b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 487b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root OpenSSLDHPrivateKey(OpenSSLKey key) { 497b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root this.key = key; 507b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 517b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 527b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 537b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public OpenSSLKey getOpenSSLKey() { 547b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return key; 557b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 567b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 577b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root OpenSSLDHPrivateKey(DHPrivateKeySpec dhKeySpec) throws InvalidKeySpecException { 587b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root try { 597b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH( 607b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root dhKeySpec.getP().toByteArray(), 617b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root dhKeySpec.getG().toByteArray(), 627b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root null, 637b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root dhKeySpec.getX().toByteArray())); 647b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } catch (Exception e) { 657b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root throw new InvalidKeySpecException(e); 667b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 677b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 687b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 697b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private void ensureReadParams() { 707b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root synchronized (mParamsLock) { 717b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (readParams) { 727b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return; 737b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 747b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 757b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root byte[][] params = NativeCrypto.get_DH_params(key.getPkeyContext()); 767b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 777b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root p = params[0]; 787b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root g = params[1]; 797b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root x = params[3]; 807b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 817b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root readParams = true; 827b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 837b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 847b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 857b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root static OpenSSLKey getInstance(DHPrivateKey dhPrivateKey) throws InvalidKeyException { 867b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root try { 877b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root DHParameterSpec dhParams = dhPrivateKey.getParams(); 887b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH( 897b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root dhParams.getP().toByteArray(), 907b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root dhParams.getG().toByteArray(), 917b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root null, 927b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root dhPrivateKey.getX().toByteArray())); 937b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } catch (Exception e) { 947b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root throw new InvalidKeyException(e); 957b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 967b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 977b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 987b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 997b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public String getAlgorithm() { 1007b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return "DH"; 1017b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1027b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1037b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1047b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public String getFormat() { 1057b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root /* 1067b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * If we're using an OpenSSL ENGINE, there's no guarantee we can export 1077b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * the key. Returning {@code null} tells the caller that there's no 1087b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * encoded format. 1097b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root */ 1107b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (key.isEngineBased()) { 1117b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return null; 1127b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1137b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1147b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return "PKCS#8"; 1157b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1167b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1177b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1187b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public byte[] getEncoded() { 1197b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root /* 1207b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * If we're using an OpenSSL ENGINE, there's no guarantee we can export 1217b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * the key. Returning {@code null} tells the caller that there's no 1227b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * encoded format. 1237b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root */ 1247b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (key.isEngineBased()) { 1257b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return null; 1267b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1277b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1287b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext()); 1297b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1307b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1317b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1327b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public DHParameterSpec getParams() { 1337b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root ensureReadParams(); 1347b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return new DHParameterSpec(new BigInteger(p), new BigInteger(g)); 1357b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1367b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1377b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1387b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public BigInteger getX() { 1397b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (key.isEngineBased()) { 1407b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root throw new UnsupportedOperationException("private key value X cannot be extracted"); 1417b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1427b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1437b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root ensureReadParams(); 1447b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return new BigInteger(x); 1457b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1467b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1477b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1487b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public boolean equals(Object o) { 1497b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (o == this) { 1507b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return true; 1517b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1527b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1537b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (o instanceof OpenSSLDHPrivateKey) { 1547b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root OpenSSLDHPrivateKey other = (OpenSSLDHPrivateKey) o; 1557b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1567b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root /* 1577b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * We can shortcut the true case, but it still may be equivalent but 1587b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root * different copies. 1597b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root */ 1607b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (key.equals(other.getOpenSSLKey())) { 1617b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return true; 1627b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1637b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1647b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1657b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (!(o instanceof DHPrivateKey)) { 1667b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return false; 1677b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1687b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1697b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root ensureReadParams(); 1707b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1717b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root final DHPrivateKey other = (DHPrivateKey) o; 1727b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (!x.equals(other.getX())) { 1737b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return false; 1747b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1757b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1767b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root DHParameterSpec spec = other.getParams(); 1777b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return g.equals(spec.getG()) && p.equals(spec.getP()); 1787b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1797b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1807b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1817b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public int hashCode() { 1827b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root ensureReadParams(); 1837b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root int hash = 1; 1847b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (!key.isEngineBased()) { 1857b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root hash = hash * 3 + x.hashCode(); 1867b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1877b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root hash = hash * 7 + p.hashCode(); 1887b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root hash = hash * 13 + g.hashCode(); 1897b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return hash; 1907b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 1917b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1927b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root @Override 1937b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root public String toString() { 1947b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root final StringBuilder sb = new StringBuilder("OpenSSLDHPrivateKey{"); 1957b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 1967b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (key.isEngineBased()) { 1977b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append("key="); 1987b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append(key); 1997b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append('}'); 2007b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return sb.toString(); 2017b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 2027b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2037b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root ensureReadParams(); 2047b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append("X="); 2057b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append(new BigInteger(x).toString(16)); 2067b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append(','); 2077b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append("P="); 2087b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append(new BigInteger(p).toString(16)); 2097b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append(','); 2107b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append("G="); 2117b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append(new BigInteger(g).toString(16)); 2127b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root sb.append('}'); 2137b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2147b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root return sb.toString(); 2157b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 2167b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2177b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 2187b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root stream.defaultReadObject(); 2197b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2207b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root final BigInteger g = (BigInteger) stream.readObject(); 2217b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root final BigInteger p = (BigInteger) stream.readObject(); 2227b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root final BigInteger x = (BigInteger) stream.readObject(); 2237b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2247b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH( 2257b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root p.toByteArray(), 2267b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root g.toByteArray(), 2277b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root null, 2287b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root x.toByteArray())); 229e526e75cfe12c2908d37b03562ac48a5bbefdf11Kenny Root mParamsLock = new Object(); 2307b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 2317b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2327b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root private void writeObject(ObjectOutputStream stream) throws IOException { 2337b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root if (getOpenSSLKey().isEngineBased()) { 2347b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root throw new NotSerializableException("engine-based keys can not be serialized"); 2357b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 2367b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2377b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root stream.defaultWriteObject(); 2387b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root 2397b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root ensureReadParams(); 2407b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root stream.writeObject(new BigInteger(g)); 2417b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root stream.writeObject(new BigInteger(p)); 2427b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root stream.writeObject(new BigInteger(x)); 2437b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root } 2447b27ca77c328e510a165712a497c20b67c68e8a3Kenny Root} 245