001package org.apache.archiva.redback.integration.filter.authentication.digest; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 023import org.apache.archiva.redback.integration.HttpUtils; 024import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticationException; 025import org.apache.commons.codec.binary.Base64; 026import org.apache.commons.lang3.StringUtils; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.springframework.context.annotation.Scope; 030import org.springframework.stereotype.Service; 031 032import java.util.Properties; 033 034/** 035 * HttpDigestHeader 036 * 037 * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a> 038 * 039 */ 040@Service( "httpClientHeader" ) 041@Scope( "prototype" ) 042public class HttpDigestHeader 043{ 044 private Logger log = LoggerFactory.getLogger( HttpDigestHeader.class ); 045 046 public String username; 047 048 public String realm; 049 050 public String nonce; 051 052 public String uri; 053 054 public String response; 055 056 public String qop; 057 058 public String nc; 059 060 public String cnonce; 061 062 public void parseClientHeader( String rawHeader, String expectedRealm, String digestKey ) 063 throws HttpAuthenticationException 064 { 065 Properties authHeaderProps = HttpUtils.complexHeaderToProperties( rawHeader, ",", "=" ); 066 067 username = authHeaderProps.getProperty( "username" ); 068 realm = authHeaderProps.getProperty( "realm" ); 069 nonce = authHeaderProps.getProperty( "nonce" ); 070 uri = authHeaderProps.getProperty( "uri" ); 071 response = authHeaderProps.getProperty( "response" ); 072 qop = authHeaderProps.getProperty( "qop" ); 073 nc = authHeaderProps.getProperty( "nc" ); 074 cnonce = authHeaderProps.getProperty( "cnonce" ); 075 076 // [RFC 2067] Validate all required values 077 if ( StringUtils.isEmpty( username ) || StringUtils.isEmpty( realm ) || StringUtils.isEmpty( nonce ) 078 || StringUtils.isEmpty( uri ) || StringUtils.isEmpty( response ) ) 079 { 080 log.debug( "Missing mandatory fields: Raw Digest Header : [{}]", rawHeader ); 081 082 throw new HttpAuthenticationException( "Missing mandatory digest fields per RFC2069." ); 083 } 084 085 // [RFC 2617] Validate realm. 086 if ( !StringUtils.equals( expectedRealm, realm ) ) 087 { 088 log.debug( "Realm name is invalid: expected [{}] but got [{}]", expectedRealm, realm ); 089 090 throw new HttpAuthenticationException( "Response realm does not match expected realm." ); 091 } 092 093 // [RFC 2617] Validate "auth" qop 094 if ( StringUtils.equals( "auth", qop ) ) 095 { 096 if ( StringUtils.isEmpty( nc ) || StringUtils.isEmpty( cnonce ) ) 097 { 098 log.debug( "Missing mandatory qop fields: nc [{}] cnonce [{}]", nc, cnonce ); 099 100 throw new HttpAuthenticationException( "Missing mandatory qop digest fields per RFC2617." ); 101 } 102 } 103 104 // [RFC 2617] Validate nonce 105 if ( !Base64.isBase64( nonce.getBytes() ) ) 106 { 107 log.debug( "Nonce is not encoded in Base64: nonce [{}]", nonce ); 108 109 throw new HttpAuthenticationException( "Response nonce is not encoded in Base64." ); 110 } 111 112 // Decode nonce 113 String decodedNonce = new String( Base64.decodeBase64( nonce.getBytes() ) ); 114 String nonceTokens[] = StringUtils.split( decodedNonce, ":" ); 115 116 // Validate nonce format 117 if ( nonceTokens.length != 2 ) 118 { 119 log.debug( "Nonce format expected [2] elements, but got [{}] instead. Decoded nonce [{}]", 120 nonceTokens.length, decodedNonce ); 121 122 throw new HttpAuthenticationException( 123 "Nonce format is invalid. " + "Received an unexpected number of sub elements." ); 124 } 125 126 // Extract nonce timestamp 127 long nonceTimestamp = 0; 128 129 try 130 { 131 nonceTimestamp = Long.parseLong( nonceTokens[0] ); 132 } 133 catch ( NumberFormatException e ) 134 { 135 throw new HttpAuthenticationException( "Unexpected nonce timestamp." ); 136 } 137 138 // Extract nonce signature 139 String expectedSignature = Digest.md5Hex( nonceTimestamp + ":" + digestKey ); 140 141 if ( !StringUtils.equals( expectedSignature, nonceTokens[1] ) ) 142 { 143 log.error( "Nonce parameter has been compromised." ); 144 145 throw new HttpAuthenticationException( "Nonce parameter has been compromised." ); 146 } 147 } 148}