1 package org.metricshub.winrm.service.client.encryption;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.util.Arrays;
26 import java.util.function.Function;
27 import java.util.function.UnaryOperator;
28 import java.util.zip.CRC32;
29 import org.apache.cxf.message.Message;
30 import org.apache.http.auth.Credentials;
31 import org.metricshub.winrm.service.client.auth.ntlm.NTCredentialsWithEncryption;
32 import org.metricshub.winrm.service.client.auth.ntlm.NTLMEngineUtils;
33
34
35
36
37
38 public class NtlmEncryptionUtils {
39
40 public static final String ENCRYPTED_BOUNDARY_PREFIX = "--Encrypted Boundary";
41 public static final String ENCRYPTED_BOUNDARY_CR = ENCRYPTED_BOUNDARY_PREFIX + "\r\n";
42 public static final String ENCRYPTED_BOUNDARY_END = ENCRYPTED_BOUNDARY_PREFIX + "--\r\n";
43
44 protected final NTCredentialsWithEncryption credentials;
45
46 private NtlmEncryptionUtils(final NTCredentialsWithEncryption credentials) {
47 this.credentials = credentials;
48 }
49
50 static NtlmEncryptionUtils of(final Credentials credentials) {
51 return credentials instanceof NTCredentialsWithEncryption
52 ? new NtlmEncryptionUtils((NTCredentialsWithEncryption) credentials)
53 : null;
54 }
55
56 static NtlmEncryptionUtils of(final Message message) {
57 final Credentials credentials = (Credentials) message.getExchange().get(Credentials.class.getName());
58 return of(credentials);
59 }
60
61 public byte[] encryptAndSign(final Message message, final byte[] messageBody) {
62 try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
63 out.write(ENCRYPTED_BOUNDARY_CR.getBytes());
64 out.write(("\tContent-Type: application/HTTP-SPNEGO-session-encrypted\r\n").getBytes());
65
66
67
68
69 out.write(
70 String
71 .format("\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=%d\r\n", messageBody.length)
72 .getBytes()
73 );
74
75 out.write(ENCRYPTED_BOUNDARY_CR.getBytes());
76 out.write("\tContent-Type: application/octet-stream\r\n".getBytes());
77
78
79
80 writeNtlmEncrypted(messageBody, out);
81
82 out.write(ENCRYPTED_BOUNDARY_END.getBytes());
83
84 message.put(
85 Message.CONTENT_TYPE,
86 "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";" +
87 "boundary=\"Encrypted Boundary\""
88 );
89 message.put(Message.ENCODING, null);
90
91 return out.toByteArray();
92 } catch (final Exception e) {
93 throw new IllegalStateException("Cannot encrypt WinRM message", e);
94 }
95 }
96
97 private byte[] seal(final byte[] in) {
98 return credentials.getStatefulEncryptor().update(in);
99 }
100
101 private void writeNtlmEncrypted(final byte[] messageBody, final ByteArrayOutputStream encrypted) throws IOException {
102 long seqNum = credentials.getSequenceNumberOutgoing().incrementAndGet();
103
104 try (
105 final ByteArrayOutputStream signatureOs = new ByteArrayOutputStream();
106 final ByteArrayOutputStream sealedOs = new ByteArrayOutputStream()
107 ) {
108
109 sealedOs.write(seal(messageBody));
110
111 calculateSignature(
112 messageBody,
113 seqNum,
114 signatureOs,
115 credentials,
116 NTCredentialsWithEncryption::getClientSigningKey,
117 this::seal
118 );
119
120 encrypted.write(ByteArrayUtils.getLittleEndianUnsignedInt(signatureOs.size()));
121 encrypted.write(signatureOs.toByteArray());
122 encrypted.write(sealedOs.toByteArray());
123 }
124 }
125
126 public void decrypt(final Message message) {
127 new Decryptor(credentials).handle(message);
128 }
129
130 static void calculateSignature(
131 final byte[] messageBody,
132 final long seqNum,
133 final ByteArrayOutputStream signature,
134 final NTCredentialsWithEncryption credentials,
135 final Function<NTCredentialsWithEncryption, byte[]> signingKeyFunction,
136 final UnaryOperator<byte[]> sealer
137 ) throws IOException {
138 if (credentials.hasNegotiateFlag(NTLMEngineUtils.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY)) {
139
140 byte[] checksum = EncryptionUtils.hmacMd5(
141 signingKeyFunction.apply(credentials),
142 ByteArrayUtils.concat(ByteArrayUtils.getLittleEndianUnsignedInt(seqNum), messageBody)
143 );
144
145 checksum = Arrays.copyOfRange(checksum, 0, 8);
146
147 if (credentials.hasNegotiateFlag(NTLMEngineUtils.NTLMSSP_NEGOTIATE_KEY_EXCH)) {
148 checksum = sealer.apply(checksum);
149 }
150
151 signature.write(new byte[] { 1, 0, 0, 0 });
152
153 signature.write(checksum);
154
155 signature.write(ByteArrayUtils.getLittleEndianUnsignedInt(seqNum));
156 } else {
157 final CRC32 crc = new CRC32();
158 crc.update(messageBody);
159 final long messageCrc = crc.getValue();
160
161
162 signature.write(new byte[] { 1, 0, 0, 0 });
163
164 signature.write(sealer.apply(ByteArrayUtils.getLittleEndianUnsignedInt(0)));
165
166 signature.write(sealer.apply(ByteArrayUtils.getLittleEndianUnsignedInt(messageCrc)));
167
168 signature.write(sealer.apply(ByteArrayUtils.getLittleEndianUnsignedInt(seqNum)));
169 }
170 }
171 }