1 package org.codehaus.redback.integration.filter.authentication.digest;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
23 import org.apache.commons.codec.binary.Base64;
24 import org.codehaus.plexus.util.StringUtils;
25 import org.codehaus.redback.integration.HttpUtils;
26 import org.codehaus.redback.integration.filter.authentication.HttpAuthenticationException;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.springframework.context.annotation.Scope;
30 import org.springframework.stereotype.Service;
32 import java.util.Properties;
37 * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
40 @Service( "httpClientHeader" )
42 public class HttpDigestHeader
44 private Logger log = LoggerFactory.getLogger( HttpDigestHeader.class );
46 public String username;
54 public String response;
62 public void parseClientHeader( String rawHeader, String expectedRealm, String digestKey )
63 throws HttpAuthenticationException
65 Properties authHeaderProps = HttpUtils.complexHeaderToProperties( rawHeader, ",", "=" );
67 username = authHeaderProps.getProperty( "username" );
68 realm = authHeaderProps.getProperty( "realm" );
69 nonce = authHeaderProps.getProperty( "nonce" );
70 uri = authHeaderProps.getProperty( "uri" );
71 response = authHeaderProps.getProperty( "response" );
72 qop = authHeaderProps.getProperty( "qop" );
73 nc = authHeaderProps.getProperty( "nc" );
74 cnonce = authHeaderProps.getProperty( "cnonce" );
76 // [RFC 2067] Validate all required values
77 if ( StringUtils.isEmpty( username ) || StringUtils.isEmpty( realm ) || StringUtils.isEmpty( nonce )
78 || StringUtils.isEmpty( uri ) || StringUtils.isEmpty( response ) )
80 log.debug( "Missing mandatory fields: Raw Digest Header : [{}]", rawHeader );
82 throw new HttpAuthenticationException( "Missing mandatory digest fields per RFC2069." );
85 // [RFC 2617] Validate realm.
86 if ( !StringUtils.equals( expectedRealm, realm ) )
88 log.debug( "Realm name is invalid: expected [{}] but got [{}]", expectedRealm, realm );
90 throw new HttpAuthenticationException( "Response realm does not match expected realm." );
93 // [RFC 2617] Validate "auth" qop
94 if ( StringUtils.equals( "auth", qop ) )
96 if ( StringUtils.isEmpty( nc ) || StringUtils.isEmpty( cnonce ) )
98 log.debug( "Missing mandatory qop fields: nc [{}] cnonce [{}]", nc, cnonce );
100 throw new HttpAuthenticationException( "Missing mandatory qop digest fields per RFC2617." );
104 // [RFC 2617] Validate nonce
105 if ( !Base64.isArrayByteBase64( nonce.getBytes() ) )
107 log.debug( "Nonce is not encoded in Base64: nonce [{}]", nonce );
109 throw new HttpAuthenticationException( "Response nonce is not encoded in Base64." );
113 String decodedNonce = new String( Base64.decodeBase64( nonce.getBytes() ) );
114 String nonceTokens[] = StringUtils.split( decodedNonce, ":" );
116 // Validate nonce format
117 if ( nonceTokens.length != 2 )
119 log.debug( "Nonce format expected [2] elements, but got [{}] instead. Decoded nonce [{}]",
120 nonceTokens.length, decodedNonce );
122 throw new HttpAuthenticationException(
123 "Nonce format is invalid. " + "Received an unexpected number of sub elements." );
126 // Extract nonce timestamp
127 long nonceTimestamp = 0;
131 nonceTimestamp = Long.parseLong( nonceTokens[0] );
133 catch ( NumberFormatException e )
135 throw new HttpAuthenticationException( "Unexpected nonce timestamp." );
138 // Extract nonce signature
139 String expectedSignature = Digest.md5Hex( nonceTimestamp + ":" + digestKey );
141 if ( !StringUtils.equals( expectedSignature, nonceTokens[1] ) )
143 log.error( "Nonce parameter has been compromised." );
145 throw new HttpAuthenticationException( "Nonce parameter has been compromised." );