diff --git a/src/main/java/ru/serega6531/packmate/service/optimization/TlsDecryptor.java b/src/main/java/ru/serega6531/packmate/service/optimization/TlsDecryptor.java index 0b63526..3fe886f 100644 --- a/src/main/java/ru/serega6531/packmate/service/optimization/TlsDecryptor.java +++ b/src/main/java/ru/serega6531/packmate/service/optimization/TlsDecryptor.java @@ -2,6 +2,7 @@ package ru.serega6531.packmate.service.optimization; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; import ru.serega6531.packmate.model.Packet; import ru.serega6531.packmate.service.optimization.tls.TlsPacket; import ru.serega6531.packmate.service.optimization.tls.keys.TlsKeyUtils; @@ -13,6 +14,7 @@ import ru.serega6531.packmate.service.optimization.tls.records.handshakes.BasicR import ru.serega6531.packmate.service.optimization.tls.records.handshakes.ClientHelloHandshakeRecordContent; import ru.serega6531.packmate.service.optimization.tls.records.handshakes.HandshakeRecordContent; import ru.serega6531.packmate.service.optimization.tls.records.handshakes.ServerHelloHandshakeRecordContent; +import ru.serega6531.packmate.utils.PRF; import ru.serega6531.packmate.utils.TlsUtils; import javax.crypto.Cipher; @@ -21,11 +23,15 @@ import java.io.File; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; @RequiredArgsConstructor public class TlsDecryptor { + private static final Pattern cipherSuitePattern = Pattern.compile("TLS_RSA_WITH_([A-Z0-9_]+)_([A-Z0-9]+)"); + private final List packets; @SneakyThrows @@ -50,7 +56,12 @@ public class TlsDecryptor { CipherSuite cipherSuite = serverHello.getCipherSuite(); - if(cipherSuite.name().startsWith("TLS_RSA_")) { + if(cipherSuite.name().startsWith("TLS_RSA_WITH_")) { + Matcher matcher = cipherSuitePattern.matcher(cipherSuite.name()); + matcher.find(); + String blockCipher = matcher.group(1); + String hashAlgo = matcher.group(2); + BasicRecordContent clientKeyExchange = (BasicRecordContent) getHandshake(tlsPackets.values(), HandshakeType.CLIENT_KEY_EXCHANGE).orElseThrow(); @@ -59,7 +70,11 @@ public class TlsDecryptor { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] preMaster = cipher.doFinal(encryptedPreMaster); + byte[] seed1 = ArrayUtils.addAll(clientRandom, serverRandom); + byte[] seed2 = ArrayUtils.addAll(serverRandom, clientRandom); + byte[] masterSecret = PRF.getBytes(preMaster, "master secret", seed1, 48); + byte[] expanded = PRF.getBytes(masterSecret, "key expansion", seed2, 136); System.out.println(); } diff --git a/src/main/java/ru/serega6531/packmate/utils/HMAC.java b/src/main/java/ru/serega6531/packmate/utils/HMAC.java new file mode 100644 index 0000000..9ec336f --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/utils/HMAC.java @@ -0,0 +1,103 @@ +/* + * Copyright 2001-2011 Joel Hockey (joel.hockey@gmail.com). All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package ru.serega6531.packmate.utils; + +import java.security.MessageDigest; + +/** + * https://github.com/joelhockey/tls/blob/master/src/main/java/net/java/jless/tls/HMAC.java + */ +public class HMAC { + + private byte[] k_ipad = new byte[64]; + private byte[] k_opad = new byte[64]; + MessageDigest md = null; + + /** + * Class constructor specifying the MessageDigest and secret to use + * + * @param md the MessageDigest (MD5 or SHA1). + * @param key the secret to seed the md. + */ + public HMAC(MessageDigest md, byte[] key) { + setMD(md); + setKey(key); + } + + /** + * Set the MessageDigest for HMAC + * + * @param md the MessageDigest + */ + public void setMD(MessageDigest md) { + this.md = md; + } + + /** + * Set the secret key for HMAC + * + * @param key the key. + */ + public void setKey(byte[] key) { + int keyLength = 0; + + // get keyLength. + if (key == null) { + keyLength = 0; + } else { + keyLength = key.length; + } + + // if the key is longer than 64 bytes then hash it. + byte[] tempKey = keyLength > 64 ? md.digest(key) : key; + + // get m_k_ipad and m_k_opad + for (int i = 0; i < keyLength; i++) { + k_ipad[i] = (byte) (0x36 ^ tempKey[i]); + k_opad[i] = (byte) (0x5C ^ tempKey[i]); + } + + for (int i = keyLength; i < 64; i++) { + k_ipad[i] = 0x36; + k_opad[i] = 0x5C; + } + } + + /** + * Digest the HMAC + * + * @param input the byte array input + * @return HMAC value + */ + public byte[] digest(byte[] input) { + + md.reset(); + md.update(k_ipad); + md.update(input); + byte[] inner = md.digest(); + md.update(k_opad); + md.update(inner); + return md.digest(); + } + +} diff --git a/src/main/java/ru/serega6531/packmate/utils/PRF.java b/src/main/java/ru/serega6531/packmate/utils/PRF.java new file mode 100644 index 0000000..9a934e4 --- /dev/null +++ b/src/main/java/ru/serega6531/packmate/utils/PRF.java @@ -0,0 +1,110 @@ +package ru.serega6531.packmate.utils; + +import lombok.experimental.UtilityClass; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Based on PRF.java + */ +@UtilityClass +public class PRF { + + private static final MessageDigest md5; + private static final MessageDigest sha; + private static final HMAC hmac = new HMAC(null, null); + + static { + try { + md5 = MessageDigest.getInstance("MD5"); + sha = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MD5 or SHA failed to initialize"); + } + } + + /** + * Generates the PRF of the given inputs + * @param secret + * @param label + * @param seed + * @param length The length of the output to generate. + * @return PRF of inputs + */ + public byte[] getBytes(byte[] secret, String label, byte[] seed, int length) { + + byte[] output = new byte[length]; + + // split secret into S1 and S2 + int lenS1 = secret.length / 2 + secret.length % 2; + + byte[] s1 = new byte[lenS1]; + byte[] s2 = new byte[lenS1]; + + System.arraycopy(secret, 0, s1, 0, lenS1); + System.arraycopy(secret, secret.length - lenS1, s2, 0, lenS1); + + // get the seed as concatenation of label and seed + byte[] labelAndSeed = new byte[label.length() + seed.length]; + System.arraycopy(label.getBytes(), 0, labelAndSeed, 0, label.length()); + System.arraycopy(seed, 0, labelAndSeed, label.length(), seed.length); + + byte[] md5Output = p_hash(md5, 16, s1, labelAndSeed, length); + byte[] shaOutput = p_hash(sha, 20, s2, labelAndSeed, length); + + // XOR md5 and sha to get output + for (int i = 0; i < length; i++) { + output[i] = (byte) (md5Output[i] ^ shaOutput[i]); + } + + return output; + } + + /** + * Perform the P_hash function + * @param md The MessageDigest function to use + * @param digestLength The length of output from the given digest + * @param secret The TLS secret + * @param seed The seed to use + * @param length The desired length of the output. + * @return The P_hash of the inputs. + */ + private byte[] p_hash(MessageDigest md, int digestLength, byte[] secret, + byte[] seed, int length) { + + // set up our hmac + hmac.setMD(md); + hmac.setKey(secret); + + byte[] output = new byte[length]; // what we return + int offset = 0; // how much data we have created so far + int toCopy = 0; // the amount of data to copy from current HMAC + + byte[] a = seed; // initialise A(0) + + // concatenation of A and seed + byte[] aSeed = new byte[digestLength + seed.length]; + System.arraycopy(seed, 0, aSeed, digestLength, seed.length); + + byte[] tempBuf = null; + + // continually perform HMACs and concatenate until we have enough output + while( offset < length ) { + + // calculate the A to use. + a = hmac.digest(a); + + // concatenate A and seed and perform HMAC + System.arraycopy(a, 0, aSeed, 0, digestLength); + tempBuf = hmac.digest(aSeed); + + // work out how much needs to be copied and copy it + toCopy = Math.min(tempBuf.length, (length - offset)); + System.arraycopy(tempBuf, 0, output, offset, toCopy); + offset += toCopy; + } + return output; + } + +}