View Javadoc
1   package uk.co.westhawk.snmp.stack;
2   // NAME
3   //      $RCSfile: AsnDecoderv3.java,v $
4   // DESCRIPTION
5   //      [given below in javadoc format]
6   // DELTA
7   //      $Revision: 3.9 $
8   // CREATED
9   //      $Date: 2009/03/05 12:48:59 $
10  // COPYRIGHT
11  //      Westhawk Ltd
12  // TO DO
13  //
14  
15  /*
16   * Copyright (C) 1995, 1996 by West Consulting BV
17   *
18   * Permission to use, copy, modify, and distribute this software
19   * for any purpose and without fee is hereby granted, provided
20   * that the above copyright notices appear in all copies and that
21   * both the copyright notice and this permission notice appear in
22   * supporting documentation.
23   * This software is provided "as is" without express or implied
24   * warranty.
25   * original version by hargrave@dellgate.us.dell.com (Jordan Hargrave)
26   */
27  
28  /*
29   * Copyright (C) 1996 - 2006 by Westhawk Ltd
30   * <a href="www.westhawk.co.uk">www.westhawk.co.uk</a>
31   *
32   * Permission to use, copy, modify, and distribute this software
33   * for any purpose and without fee is hereby granted, provided
34   * that the above copyright notices appear in all copies and that
35   * both the copyright notice and this permission notice appear in
36   * supporting documentation.
37   * This software is provided "as is" without express or implied
38   * warranty.
39   * author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
40   */
41  
42  /*-
43   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
44   * SNMP Java Client
45   * ჻჻჻჻჻჻
46   * Copyright 2023 MetricsHub, Westhawk
47   * ჻჻჻჻჻჻
48   * This program is free software: you can redistribute it and/or modify
49   * it under the terms of the GNU Lesser General Public License as
50   * published by the Free Software Foundation, either version 3 of the
51   * License, or (at your option) any later version.
52   *
53   * This program is distributed in the hope that it will be useful,
54   * but WITHOUT ANY WARRANTY; without even the implied warranty of
55   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
56   * GNU General Lesser Public License for more details.
57   *
58   * You should have received a copy of the GNU General Lesser Public
59   * License along with this program.  If not, see
60   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
61   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
62   */
63  
64  import java.io.ByteArrayInputStream;
65  import java.io.IOException;
66  import java.io.InputStream;
67  
68  import uk.co.westhawk.snmp.util.SnmpUtilities;
69  
70  /**
71   * This class contains the v3 specific methods to decode bytes into a Pdu.
72   * We split the original class AsnDecoder into four classes.
73   *
74   * @since 4_14
75   * @author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
76   * @version $Revision: 3.9 $ $Date: 2009/03/05 12:48:59 $
77   */
78  class AsnDecoderv3 extends AsnDecoderBase implements usmStatsConstants {
79  	private static final String version_id = "@(#)$Id: AsnDecoderv3.java,v 3.9 2009/03/05 12:48:59 birgita Exp $ Copyright Westhawk Ltd";
80  
81  	/**
82  	 * Returns the msgId of the SNMPv3 asn sequence.
83  	 */
84  	int getMessageId(AsnSequence asnTopSeq) throws DecodingException {
85  		int msgId = -1;
86  		AsnSequence asnHeaderData = getAsnHeaderData(asnTopSeq);
87  		AsnObject obj = asnHeaderData.getObj(0);
88  		if (obj instanceof AsnInteger) {
89  			AsnInteger value = (AsnInteger) obj;
90  			msgId = value.getValue();
91  		} else {
92  			String msg = "msgId should be of type AsnInteger"
93  					+ " instead of " + obj.getRespTypeString();
94  			throw new DecodingException(msg);
95  		}
96  		return msgId;
97  	}
98  
99  	/**
100 	 * This method creates an AsnPduSequence out of the characters of the
101 	 * InputStream for v3.
102 	 *
103 	 * @see AbstractSnmpContext#run
104 	 * @see SnmpContextv3#processIncomingResponse
105 	 * @see SnmpContextv3#processIncomingPdu
106 	 */
107 	AsnSequence DecodeSNMPv3(InputStream in)
108 			throws IOException, DecodingException {
109 		AsnSequence asnTopSeq = getAsnSequence(in);
110 		int snmpVersion = getSNMPVersion(asnTopSeq);
111 		if (snmpVersion != SnmpConstants.SNMP_VERSION_3) {
112 			String str = SnmpUtilities.getSnmpVersionString(snmpVersion);
113 			String msg = "Wrong SNMP version: expected SNMPv3, received "
114 					+ str;
115 			throw new DecodingException(msg);
116 		} else {
117 			int securityModel = -1;
118 			AsnSequence asnHeaderData = getAsnHeaderData(asnTopSeq);
119 			AsnObject obj = asnHeaderData.getObj(3);
120 			if (obj instanceof AsnInteger) {
121 				AsnInteger value = (AsnInteger) obj;
122 				securityModel = value.getValue();
123 				if (securityModel != SnmpContextv3Face.USM_Security_Model) {
124 					String msg = "Wrong v3 Security Model: expected USM("
125 							+ SnmpContextv3Face.USM_Security_Model
126 							+ "), received "
127 							+ securityModel;
128 					throw new DecodingException(msg);
129 				}
130 			} else {
131 				String msg = "securityModel should be of type AsnInteger"
132 						+ " instead of " + obj.getRespTypeString();
133 				throw new DecodingException(msg);
134 			}
135 		}
136 		return asnTopSeq;
137 	}
138 
139 	/**
140 	 * Processes the SNMP v3 AsnSequence. See section 3.2 of
141 	 * <a href="http://www.ietf.org/rfc/rfc3414.txt">SNMP-USER-BASED-SM-MIB</a>.<br>
142 	 * See <a href="http://www.ietf.org/rfc/rfc7630.txt">HMAC-SHA-2 Authentication
143 	 * Protocols</a>.
144 	 * 
145 	 * @param context          The SnmpContextv3Basis instance context
146 	 * @param asnTopSeq        The AsnSequence
147 	 * @param message          The byte array of the message
148 	 * @param amIAuthoritative boolean to indicate if we are authoritative
149 	 * @return The AsnPduSequence or null
150 	 */
151 	AsnPduSequence processSNMPv3(SnmpContextv3Basis context, AsnSequence asnTopSeq, byte[] message,
152 			boolean amIAuthoritative) throws IOException, DecodingException {
153 		AsnPduSequence pduSeq = null;
154 
155 		// if not correct, I'll just skip a lot of tests.
156 		boolean isCorrect = asnTopSeq.isCorrect;
157 
158 		AsnSequence asnHeaderData = getAsnHeaderData(asnTopSeq);
159 		// int msgId = ((AsnInteger)asnHeaderData.getObj(0)).getValue();
160 		// int maxSize = ((AsnInteger)asnHeaderData.getObj(1)).getValue();
161 		byte[] msgFlags = ((AsnOctets) asnHeaderData.getObj(2)).getBytes();
162 		boolean isUseAuthentication = isUseAuthentication(msgFlags[0]);
163 		boolean isUsePrivacy = isUsePrivacy(msgFlags[0]);
164 
165 		AsnOctets asnSecurityParameters = (AsnOctets) asnTopSeq.getObj(2);
166 		AsnSequence usmObject = decodeUSM(asnSecurityParameters);
167 
168 		byte[] engineIdBytes = ((AsnOctets) usmObject.getObj(0)).getBytes();
169 		String engineId = SnmpUtilities.toHexString(engineIdBytes);
170 		int boots = ((AsnInteger) usmObject.getObj(1)).getValue();
171 		int time = ((AsnInteger) usmObject.getObj(2)).getValue();
172 		String userName = ((AsnOctets) usmObject.getObj(3)).getValue();
173 		AsnOctets realFingerPrintObject = (AsnOctets) usmObject.getObj(4);
174 		byte[] realFingerPrint = realFingerPrintObject.getBytes();
175 		byte[] salt = ((AsnOctets) usmObject.getObj(5)).getBytes();
176 
177 		TimeWindow timeWindow = TimeWindow.getCurrent();
178 		if (amIAuthoritative == false) {
179 			/*
180 			 * engineId should not be empty, however net-snmp 5.3.0.1 has a bug (1427410) in
181 			 * their trapsess; it sends an empty engineId
182 			 */
183 			if (engineId.length() > 0
184 					&& timeWindow.isEngineIdOK(context.getReceivedFromHostAddress(), context.getPort(),
185 							engineId) == false) {
186 				String msg = "Received engine Id ('" + engineId + "') is not correct.";
187 				msg += " amIAuthoritative == false";
188 				throw new DecodingException(msg);
189 			} else {
190 				// This should only happen once, if for some reason the sendto
191 				// and receivedfrom addresses are different.
192 				// It is a hack, but it needs to seem like the 'sendToHostAddress'
193 				// has been discovered, else any other request sent to this
194 				// address will try to do another discovery and the process
195 				// starts again.
196 				String sendToHostAddress = context.getSendToHostAddress();
197 				String receivedFromHostAddress = context.getReceivedFromHostAddress();
198 				if (sendToHostAddress.equals(receivedFromHostAddress) == false) {
199 					String storedEngineId;
200 					storedEngineId = timeWindow.getSnmpEngineId(sendToHostAddress, context.getPort());
201 					if (storedEngineId == null) {
202 						timeWindow.setSnmpEngineId(sendToHostAddress, context.getPort(), "00");
203 					}
204 				}
205 			}
206 		} else {
207 			// amIAuthoritative == true
208 			// Section 3.2 rfc
209 			// engineId of length '0' -> discovery.
210 			if (engineId.length() > 0
211 					&& timeWindow.isEngineIdOK(context.getUsmAgent().MYFAKEHOSTNAME, context.getPort(),
212 							engineId) == false) {
213 				String msg = "Received engine Id ('" + engineId + "') is not correct.";
214 				msg += " amIAuthoritative == true";
215 				throw new DecodingException(msg);
216 			}
217 		}
218 
219 		if (userName.equals(context.getUserName()) == false) {
220 			String msg = "Received userName ('" + userName + "') is not correct";
221 			throw new DecodingException(msg);
222 		}
223 
224 		// I'm not really supposed to encrypt before checking and doing
225 		// authentication, but I would like to use the pduSeq
226 		// So, I'll encrypt and save the possible exception.
227 		DecodingException encryptionDecodingException = null;
228 		IOException encryptionIOException = null;
229 		int authenticationProtocol = context.getAuthenticationProtocol();
230 		try {
231 			AsnObject asnScopedObject = asnTopSeq.getObj(3);
232 			AsnSequence asnPlainScopedPdu = null;
233 			if (isUsePrivacy == true) {
234 				int privacyProtocol = context.getPrivacyProtocol();
235 				// Retrieves the localized privacy key from the derived privacy key
236 				byte[] privacyKey = context.generatePrivacyKey(engineId, authenticationProtocol, privacyProtocol);
237 
238 				AsnOctets asnEncryptedScopedPdu = (AsnOctets) asnScopedObject;
239 				byte[] encryptedText = asnEncryptedScopedPdu.getBytes();
240 
241 				byte[] plainText = null;
242 				if (SnmpContextv3Basis.AES_PRIVACY_PROTOCOLS.contains(privacyProtocol)){
243 					plainText = SnmpUtilities.AESdecrypt(encryptedText, privacyKey, boots, time, salt);
244 				} else {
245 					plainText = SnmpUtilities.DESdecrypt(encryptedText, salt, privacyKey);
246 				}
247 
248 				if (AsnObject.debug > 10) {
249 					System.out.println("Encrypted PDU: ");
250 					System.out.println("Decoding with : " + SnmpContextv3Basis.PROTOCOL_NAMES[privacyProtocol]);
251 				}
252 
253 				ByteArrayInputStream plainIn = new ByteArrayInputStream(plainText);
254 				asnPlainScopedPdu = getAsnSequence(plainIn);
255 			} else {
256 				asnPlainScopedPdu = (AsnSequence) asnScopedObject;
257 			}
258 			pduSeq = (AsnPduSequence) asnPlainScopedPdu.findPdu();
259 		} catch (DecodingException exc) {
260 			encryptionDecodingException = exc;
261 		} catch (IOException exc) {
262 			encryptionIOException = exc;
263 		}
264 		if (pduSeq != null && engineId.length() == 0) {
265 			pduSeq.setSnmpv3Discovery(true);
266 		}
267 
268 		boolean userIsUsingAuthentication = context.isUseAuthentication();
269 		if (isCorrect == true && (isUseAuthentication != userIsUsingAuthentication)) {
270 			String msg = "User " + userName + " does ";
271 			if (userIsUsingAuthentication == false) {
272 				msg += "not ";
273 			}
274 			msg += "support authentication, but received message ";
275 
276 			if (isUseAuthentication) {
277 				msg += "with authentication.";
278 			} else {
279 				msg += "without authentication";
280 				msg += getUsmStats(pduSeq);
281 			}
282 			throw new DecodingException(msg);
283 		}
284 
285 		boolean isAuthentic = false;
286 		if (isCorrect == true && isUseAuthentication == true) {
287 			int fpPos = realFingerPrintObject.getContentsPos();
288 			if (AsnObject.debug > 10) {
289 				int fpLength = realFingerPrintObject.getContentsLength();
290 				String str = "Pos finger print = " + fpPos + ", len = " + fpLength;
291 				SnmpUtilities.dumpBytes(str, realFingerPrint);
292 			}
293 
294 			byte[] computedFingerprint = null;
295 			// Init the fingerprint
296 			byte[] dummyFingerPrint = SnmpUtilities.initFingerprint(authenticationProtocol);
297 			System.arraycopy(dummyFingerPrint, 0, message, fpPos, realFingerPrint.length);
298 
299 			// Calculate the fingerprint
300 			computedFingerprint = context.computeFingerprint(engineId, authenticationProtocol, computedFingerprint,
301 					message);
302 
303 			if (SnmpUtilities.areBytesEqual(realFingerPrint, computedFingerprint) == false) {
304 				String msg = "Authentication comparison failed";
305 				throw new DecodingException(msg);
306 			} else {
307 				if (pduSeq != null && boots == 0 && time == 0) {
308 					pduSeq.setSnmpv3Discovery(true);
309 				}
310 				if (timeWindow.isOutsideTimeWindow(engineId, boots, time)) {
311 					String msg = "Message is outside time window";
312 					throw new DecodingException(msg);
313 				}
314 				isAuthentic = true;
315 			}
316 		}
317 		timeWindow.updateTimeWindow(engineId, boots, time, isAuthentic);
318 
319 		boolean userIsUsingPrivacy = context.isUsePrivacy();
320 		if (isCorrect == true && (isUsePrivacy != userIsUsingPrivacy)) {
321 			String msg = "User " + userName + " does ";
322 			if (userIsUsingPrivacy == false) {
323 				msg += "not ";
324 			}
325 			msg += "support privacy, but received message ";
326 			if (isUsePrivacy) {
327 				msg += "with privacy.";
328 			} else {
329 				msg += "without privacy";
330 				msg += getUsmStats(pduSeq);
331 			}
332 			throw new DecodingException(msg);
333 		}
334 
335 		if (encryptionDecodingException != null) {
336 			throw encryptionDecodingException;
337 		}
338 		if (encryptionIOException != null) {
339 			throw encryptionIOException;
340 		}
341 
342 		if (pduSeq != null && isCorrect == false) {
343 			pduSeq.isCorrect = false;
344 		}
345 		return pduSeq;
346 	}
347 
348 	private boolean isUseAuthentication(byte msgFlags) {
349 		boolean isUseAuthentication = ((byte) (0x01) & msgFlags) > 0;
350 		return isUseAuthentication;
351 	}
352 
353 	private boolean isUsePrivacy(byte msgFlags) {
354 		boolean isUsePrivacy = ((byte) (0x02) & msgFlags) > 0;
355 		return isUsePrivacy;
356 	}
357 
358 	private AsnSequence decodeUSM(AsnOctets asnSecurityParameters)
359 			throws IOException {
360 		byte[] usmBytes = asnSecurityParameters.getBytes();
361 		if (AsnObject.debug > 10) {
362 			SnmpUtilities.dumpBytes("Decoding USM:", usmBytes);
363 		}
364 
365 		ByteArrayInputStream usmIn = new ByteArrayInputStream(usmBytes);
366 		AsnSequence usmOctets = new AsnSequence(usmIn, usmBytes.length,
367 				asnSecurityParameters.getContentsPos());
368 		AsnSequence usmObject = (AsnSequence) usmOctets.getObj(0);
369 		return usmObject;
370 	}
371 
372 	/**
373 	 * Sometimes when an error occurs the usmStats is sent in the varbind
374 	 * list.
375 	 */
376 	private String getUsmStats(AsnPduSequence pduSeq) {
377 		String msg = "";
378 		AsnSequence varBind = (AsnSequence) pduSeq.getObj(3);
379 		int size = varBind.getObjCount();
380 		if (size > 0) {
381 			AsnSequence varSeq = (AsnSequence) varBind.getObj(0);
382 			varbind vb = new varbind(varSeq);
383 			AsnObjectId oid = vb.getOid();
384 			boolean found = false;
385 			int i = 0;
386 			while (i < usmStatsOids.length && found == false) {
387 				AsnObjectId usmOid = new AsnObjectId(usmStatsOids[i]);
388 				found = (oid.startsWith(usmOid) == true);
389 				i++;
390 			}
391 			if (found == true) {
392 				i--;
393 				msg += ": " + usmStatsStrings[i] + " " + vb.getValue();
394 			} else {
395 				msg += ": " + vb;
396 			}
397 		}
398 		return msg;
399 	}
400 
401 }