com.hierynomus.smbj.connection.packet.SMB3DecryptingPacketHandler Maven / Gradle / Ivy
/*
* Copyright (C)2016 - SMBJ Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hierynomus.smbj.connection.packet;
import com.hierynomus.mssmb2.*;
import com.hierynomus.protocol.commons.buffer.Buffer;
import com.hierynomus.protocol.transport.TransportException;
import com.hierynomus.smb.SMBPacketData;
import com.hierynomus.smbj.common.SMBRuntimeException;
import com.hierynomus.smbj.connection.PacketEncryptor;
import com.hierynomus.smbj.connection.SessionTable;
import com.hierynomus.smbj.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* 3.2.5.1.1 Decrypting the Message
*
* This section is applicable for only the SMB 3.x dialect family.<149>
*
* The client MUST perform the following:
*
* If the size of the message received from the server is not greater than the size of SMB2 TRANSFORM_HEADER as
* specified in section 2.2.41, the client MUST discard the message.
*
* If the Flags/EncryptionAlgorithm in the SMB2 TRANSFORM_HEADER is not 0x0001, the client MUST discard the message.
*
* The client MUST look up the session in the Connection.SessionTable using the SessionId in the
* SMB2 TRANSFORM_HEADER of the response. If the session is not found, the response MUST be discarded.
*
* The client MUST decrypt the message using Session.DecryptionKey. If Connection.Dialect is "3.1.1", the algorithm
* specified by Connection.CipherId is used. Otherwise, the AES-128-CCM algorithm is used. The client passes in the
* Nonce, OriginalMessageSize, Flags/EncryptionAlgorithm, and SessionId fields of the SMB2 TRANSFORM_HEADER and the
* encrypted SMB2 message as the Optional Authenticated Data input for the algorithm. If decryption succeeds, the
* client MUST compare the signature in the SMB2 TRANSFORM_HEADER with the signature returned by the decryption
* algorithm. If signature verification fails, the client MUST fail the application request with an
* implementation-specific error.
*
* If signature verification succeeds, the client MUST perform the following:
*
* - If ProtocolId in the header of the decrypted message is 0x424d53FD indicating a nested encrypted message,
* the client MUST disconnect the connection.
* - If ProtocolId in the header of the decrypted message is 0x424d53FC indicating a nested compressed message,
* the client MUST decompress the message as specified in section 3.2.5.1.10.
*
* If decompression succeeds, the client MUST further validate the message:
*
* - If the NextCommand field in the first SMB2 header of the message is equal to 0 and SessionId of the
* first SMB2 header is not equal to the SessionId field in SMB2 TRANSFORM_HEADER of response,
* the client MUST discard the message.
* - For each response in a compounded response, if the SessionId field of SMB2 header is not equal to the
* SessionId field in the SMB2 TRANSFORM_HEADER, the client SHOULD<150> discard the entire compounded response
* and stop processing.
*
*
* - If ProtocolId in the header of the decrypted message is 0x424d53FE indicating an SMB2 header, the client
* MUST further validate the decrypted message:
*
* - If the NextCommand field in the first SMB2 header of the message is equal to 0 and SessionId of the
* first SMB2 header is not equal to the SessionId field in SMB2 TRANSFORM_HEADER of response,
* the client MUST discard the message.
* - For each response in a compounded response, if the SessionId field of SMB2 header is not equal to the
* SessionId field in the SMB2 TRANSFORM_HEADER, the client SHOULD<151> discard the entire compounded response
* and stop processing.
*
*
* - Otherwise, the client MUST disconnect the connection.
*
*/
public class SMB3DecryptingPacketHandler extends AbstractIncomingPacketHandler {
private static final Logger logger = LoggerFactory.getLogger(SMB3DecryptingPacketHandler.class);
private SessionTable sessionTable;
private PacketEncryptor encryptor;
public SMB3DecryptingPacketHandler(SessionTable sessionTable, PacketEncryptor encryptor) {
this.sessionTable = sessionTable;
this.encryptor = encryptor;
}
@Override
protected boolean canHandle(SMBPacketData packetData) {
return packetData instanceof SMB3EncryptedPacketData;
}
@Override
protected void doHandle(SMBPacketData packetData) throws TransportException {
SMB3EncryptedPacketData data = (SMB3EncryptedPacketData) packetData;
logger.debug("Decrypting packet {}", data);
if (!encryptor.canDecrypt(data)) {
next.handle(new DeadLetterPacketData(packetData.getHeader()));
return;
}
long sessionId = data.getHeader().getSessionId();
Session session = sessionTable.find(sessionId);
if (session == null) {
next.handle(new DeadLetterPacketData(packetData.getHeader()));
return;
}
byte[] decrypted = encryptor.decrypt(data, session.getSessionContext().getDecryptionKey());
byte[] decryptedProtocolId = Arrays.copyOf(decrypted, 4);
if (Arrays.equals(decryptedProtocolId, SMB2TransformHeader.ENCRYPTED_PROTOCOL_ID)) {
logger.error("Encountered a nested encrypted packet in packet {}, disconnecting the transport", packetData);
throw new TransportException("Cannot nest an encrypted packet in encrypted packet " + packetData);
} else if (Arrays.equals(decryptedProtocolId, SMB2CompressionTransformHeader.COMPRESSED_PROTOCOL_ID)) {
handleCompressedPacket(packetData, decrypted);
return;
} else if (Arrays.equals(decryptedProtocolId, SMB2PacketHeader.PROTOCOL_ID)) {
handleSMB2Packet(decrypted, data);
return;
} else {
logger.error("Could not determine the encrypted packet contents of packet {}", packetData);
throw new TransportException("Could not determine the encrypted packet data, disconnecting");
}
}
private void handleCompressedPacket(SMBPacketData packetData, byte[] decrypted) throws TransportException {
logger.debug("Packet {} is compressed.", packetData);
try {
next.handle(new SMB3CompressedPacketData(decrypted, true));
// TODO not handling further decompression validation
return;
} catch (Buffer.BufferException e) {
throw new SMBRuntimeException("Could not load compression header", e);
}
}
private void handleSMB2Packet(byte[] decrypted, SMB3EncryptedPacketData packetData) throws TransportException {
try {
SMB2PacketData nextPacket = new SMB2DecryptedPacketData(decrypted);
logger.debug("Decrypted packet {} is packet {}.", packetData, nextPacket);
if (nextPacket.getHeader().getSessionId() != packetData.getHeader().getSessionId()) {
logger.error("Mismatched sessionId between encrypted packet {} and decrypted contents {}", packetData, nextPacket);
next.handle(new DeadLetterPacketData(nextPacket.getHeader()));
} else {
next.handle(nextPacket);
// TODO handle compounded session id validation...
}
} catch (Buffer.BufferException e) {
throw new SMBRuntimeException("Could not load SMB2 Packet", e);
}
}
}