View Javadoc
1   package org.metricshub.winrm.service.client.encryption;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 - 2024 Metricshub
8    * ჻჻჻჻჻჻
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21   */
22  
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.util.Objects;
26  import org.apache.cxf.io.CachedOutputStream;
27  import org.apache.cxf.message.Message;
28  import org.apache.http.auth.Credentials;
29  import org.metricshub.winrm.service.client.auth.ntlm.NTCredentialsWithEncryption;
30  
31  /**
32   * Code from io.cloudsoft.winrm4j.client.encryption.SignAndEncryptOutInterceptor.EncryptAndSignOutputStream
33   * release 0.12.3 @link https://github.com/cloudsoft/winrm4j
34   */
35  class EncryptAndSignOutputStream extends CachedOutputStream {
36  
37  	private final CachedOutputStream unencrypted;
38  	private ContentWithType unencryptedResult = null;
39  	private ContentWithType encrypted = null;
40  	private final Message message;
41  
42  	private OutputStream wrapped;
43  
44  	private NTCredentialsWithEncryption credentials;
45  
46  	public EncryptAndSignOutputStream(final Message message, final OutputStream outputStream) {
47  		super();
48  		this.message = message;
49  		wrapped = outputStream;
50  		unencrypted = new CachedOutputStream();
51  
52  		final Object creds = message.get(Credentials.class.getName());
53  		if (creds instanceof NTCredentialsWithEncryption) {
54  			credentials = (NTCredentialsWithEncryption) creds;
55  		}
56  	}
57  
58  	@Override
59  	public void resetOut(final OutputStream outputStream, final boolean copyOldContent) throws IOException {
60  		super.resetOut(outputStream, copyOldContent);
61  	}
62  
63  	@Override
64  	public void close() throws IOException {
65  		super.close();
66  		unencrypted.write(getBytes());
67  		currentStream = NullOutputStream.NULL_OUTPUT_STREAM;
68  
69  		if (wrapped != null) {
70  			processAndShip(wrapped);
71  			wrapped.close();
72  		}
73  	}
74  
75  	private synchronized ContentWithType getEncrypted() {
76  		try {
77  			if (encrypted == null) {
78  				final byte[] bytesEncryptedAndSigned = NtlmEncryptionUtils
79  					.of(credentials)
80  					.encryptAndSign(message, unencrypted.getBytes());
81  
82  				encrypted = ContentWithType.of(message, bytesEncryptedAndSigned);
83  			}
84  			return encrypted;
85  		} catch (final IOException e) {
86  			throw new IllegalStateException(e);
87  		}
88  	}
89  
90  	private byte[] getUnencrypted() {
91  		try {
92  			return unencrypted.getBytes();
93  		} catch (final IOException e) {
94  			throw new IllegalStateException(e);
95  		}
96  	}
97  
98  	synchronized ContentWithType getAppropriate() {
99  		if (unencryptedResult == null) {
100 			unencryptedResult = ContentWithType.of(message, null);
101 		}
102 
103 		if (credentials == null || !credentials.isAuthenticated()) {
104 			if (encrypted != null) {
105 				// clear any previous encryption if no longer valid
106 				encrypted = null;
107 			}
108 
109 			return credentials != null && !credentials.isAuthenticated()
110 				? unencryptedResult.with(AsyncHttpEncryptionAwareConduit.PRE_AUTH_BOGUS_PAYLOAD)
111 				: unencryptedResult.with(getUnencrypted());
112 		}
113 
114 		return getEncrypted();
115 	}
116 
117 	private void processAndShip(final OutputStream output) throws IOException {
118 		output.write(getAppropriate().getPayload());
119 		output.close();
120 	}
121 
122 	@Override
123 	public int hashCode() {
124 		final int prime = 31;
125 		int result = super.hashCode();
126 		result = prime * result + Objects.hash(credentials, encrypted, message, unencrypted, unencryptedResult, wrapped);
127 		return result;
128 	}
129 
130 	@Override
131 	public boolean equals(Object obj) {
132 		if (this == obj) return true;
133 		if (!super.equals(obj)) return false;
134 		if (!(obj instanceof EncryptAndSignOutputStream)) return false;
135 		EncryptAndSignOutputStream other = (EncryptAndSignOutputStream) obj;
136 		return (
137 			Objects.equals(credentials, other.credentials) &&
138 			Objects.equals(encrypted, other.encrypted) &&
139 			Objects.equals(message, other.message) &&
140 			Objects.equals(unencrypted, other.unencrypted) &&
141 			Objects.equals(unencryptedResult, other.unencryptedResult) &&
142 			Objects.equals(wrapped, other.wrapped)
143 		);
144 	}
145 }