1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 package org.apache.hc.client5.http.protocol; 29 30 import org.apache.hc.core5.annotation.Contract; 31 import org.apache.hc.core5.annotation.ThreadingBehavior; 32 import org.apache.hc.core5.http.EntityDetails; 33 import org.apache.hc.core5.http.FormattedHeader; 34 import org.apache.hc.core5.http.Header; 35 import org.apache.hc.core5.http.HttpResponse; 36 import org.apache.hc.core5.http.HttpResponseInterceptor; 37 import org.apache.hc.core5.http.message.ParserCursor; 38 import org.apache.hc.core5.http.protocol.HttpContext; 39 import org.apache.hc.core5.util.Args; 40 import org.apache.hc.core5.util.CharArrayBuffer; 41 import org.apache.hc.core5.util.TextUtils; 42 import org.apache.hc.core5.util.Tokenizer; 43 44 /** 45 * {@code NextNonceInterceptor} is an HTTP response interceptor that extracts the {@code nextnonce} 46 * parameter from the {@code Authentication-Info} header of an HTTP response. This parameter is used 47 * in HTTP Digest Access Authentication to provide an additional nonce value that the client is expected 48 * to use in subsequent authentication requests. By retrieving and storing this {@code nextnonce} value, 49 * the interceptor facilitates one-time nonce implementations and prevents replay attacks by ensuring that 50 * each request/response interaction includes a fresh nonce. 51 * <p> 52 * If present, the extracted {@code nextnonce} value is stored in the {@link HttpContext} under the attribute 53 * {@code auth-nextnonce}, allowing it to be accessed in subsequent requests. If the header does not contain 54 * the {@code nextnonce} parameter, no context attribute is set. 55 * </p> 56 * 57 * <p>This implementation adheres to the HTTP/1.1 specification, particularly focusing on the {@code Digest} 58 * scheme as defined in HTTP Digest Authentication, and parses header tokens using the {@link Tokenizer} 59 * utility class for robust token parsing.</p> 60 * 61 * <p>In the context of HTTP Digest Access Authentication, the {@code nextnonce} parameter is 62 * a critical part of the security mechanism, designed to mitigate replay attacks and enhance mutual 63 * authentication security. It provides the server with the ability to set and enforce single-use or 64 * session-bound nonces, prompting the client to use the provided {@code nextnonce} in its next request. 65 * This setup helps secure communication by forcing new cryptographic material in each transaction. 66 * </p> 67 * 68 * <p>This interceptor is stateless and thread-safe, making it suitable for use across multiple 69 * threads and HTTP requests. It should be registered with the HTTP client to enable support 70 * for advanced authentication mechanisms that require tracking of nonce values.</p> 71 * 72 * @since 5.5 73 */ 74 75 @Contract(threading = ThreadingBehavior.STATELESS) 76 public class NextNonceInterceptor implements HttpResponseInterceptor { 77 78 public static final HttpResponseInterceptor INSTANCE = new NextNonceInterceptor(); 79 80 private final Tokenizer tokenParser = Tokenizer.INSTANCE; 81 82 83 private static final String AUTHENTICATION_INFO_HEADER = "Authentication-Info"; 84 85 private static final Tokenizer.Delimiter TOKEN_DELIMS = Tokenizer.delimiters('=', ','); 86 private static final Tokenizer.Delimiter VALUE_DELIMS = Tokenizer.delimiters(','); 87 88 /** 89 * Processes the HTTP response and extracts the {@code nextnonce} parameter from the 90 * {@code Authentication-Info} header if available, storing it in the provided {@code context}. 91 * 92 * @param response the HTTP response containing the {@code Authentication-Info} header 93 * @param entity the response entity, ignored by this interceptor 94 * @param context the HTTP context in which to store the {@code nextnonce} parameter 95 * @throws NullPointerException if either {@code response} or {@code context} is null 96 */ 97 @Override 98 public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) { 99 Args.notNull(response, "HTTP response"); 100 Args.notNull(context, "HTTP context"); 101 102 final Header header = response.getFirstHeader(AUTHENTICATION_INFO_HEADER); 103 if (header != null) { 104 final String nextNonce; 105 if (header instanceof FormattedHeader) { 106 final CharSequence buf = ((FormattedHeader) header).getBuffer(); 107 final ParserCursor cursor = new ParserCursor(((FormattedHeader) header).getValuePos(), buf.length()); 108 nextNonce = parseNextNonce(buf, cursor); 109 } else { 110 final CharSequence headerValue = header.getValue(); 111 final ParserCursor cursor = new ParserCursor(0, headerValue.length()); 112 nextNonce = parseNextNonce(headerValue, cursor); 113 } 114 if (!TextUtils.isBlank(nextNonce)) { 115 HttpClientContext.castOrCreate(context).setNextNonce(nextNonce); 116 } 117 } 118 } 119 120 /** 121 * Parses the {@code Authentication-Info} header content represented by a {@link CharArrayBuffer} 122 * to extract the {@code nextnonce} parameter. 123 * 124 * @param buffer the {@link CharArrayBuffer} containing the value of the {@code Authentication-Info} header 125 * @param cursor the {@link ParserCursor} used to navigate through the buffer content 126 * @return the extracted {@code nextnonce} parameter value, or {@code null} if the parameter is not found 127 */ 128 private String parseNextNonce(final CharSequence buffer, final ParserCursor cursor) { 129 while (!cursor.atEnd()) { 130 final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS); 131 if ("nextnonce".equals(name)) { 132 cursor.updatePos(cursor.getPos() + 1); 133 return tokenParser.parseValue(buffer, cursor, VALUE_DELIMS); 134 } 135 if (!cursor.atEnd()) { 136 cursor.updatePos(cursor.getPos() + 1); 137 } 138 } 139 return null; 140 } 141 142 }