/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package io.milton.http.http11.auth; import io.milton.http.Auth; import io.milton.http.Request.Method; import io.milton.http.http11.auth.NonceProvider.NonceValidity; import java.io.UnsupportedEncodingException; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * */ public class DigestHelper { private static final Logger log = LoggerFactory.getLogger( DigestHelper.class ); private final NonceProvider nonceProvider; public DigestHelper(NonceProvider nonceProvider) { this.nonceProvider = nonceProvider; } public DigestResponse calculateResponse( Auth auth, String expectedRealm, Method method ) { try { // Check all required parameters were supplied (ie RFC 2069) if( ( auth.getUser() == null ) || ( auth.getRealm() == null ) || ( auth.getNonce() == null ) || ( auth.getUri() == null ) ) { log.warn( "missing params" ); return null; } // Check all required parameters for an "auth" qop were supplied (ie RFC 2617) Long nc; if( "auth".equals( auth.getQop() ) ) { if( ( auth.getNc() == null ) || ( auth.getCnonce() == null ) ) { log.warn( "missing params: nc and/or cnonce" ); return null; } nc = Long.parseLong( auth.getNc(), 16); // the nonce-count. hex value, must always increase } else { nc = null; } // Check realm name equals what we expected if( expectedRealm == null ) throw new IllegalStateException( "realm is null"); if( !expectedRealm.equals( auth.getRealm() ) ) { log.warn( "incorrect realm: resource: " + expectedRealm + " given: " + auth.getRealm() ); return null; } // Check nonce was a Base64 encoded (as sent by DigestProcessingFilterEntryPoint) if( !Base64.isArrayByteBase64( auth.getNonce().getBytes("UTF-8") ) ) { log.warn( "nonce not base64 encoded" ); return null; } log.debug( "nc: " + auth.getNc()); // Decode nonce from Base64 // format of nonce is // base64(expirationTime + "" + md5Hex(expirationTime + "" + key)) String plainTextNonce = new String( Base64.decodeBase64( auth.getNonce().getBytes("UTF-8") ) ); NonceValidity validity = nonceProvider.getNonceValidity( plainTextNonce, nc ); // if( NonceValidity.INVALID.equals( validity ) ) { // log.debug( "invalid nonce: " + plainTextNonce ); // return null; // } else if( NonceValidity.EXPIRED.equals( validity ) ) { // log.debug( "expired nonce: " + plainTextNonce ); // // make this known so that we can add stale field to challenge // auth.setNonceStale( true ); // return null; // } DigestResponse resp = toDigestResponse( auth, method ); return resp; } catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); } } public String getChallenge( String nonceValue, Auth auth, String actualRealm ) { try { String nonceValueBase64 = new String( Base64.encodeBase64( nonceValue.getBytes("UTF-8") ) ); // qop is quality of protection, as defined by RFC 2617. // we do not use opaque due to IE violation of RFC 2617 in not // representing opaque on subsequent requests in same session. String authenticateHeader = "Digest realm=\"" + actualRealm + "\", " + "qop=\"auth\", nonce=\"" + nonceValueBase64 + "\""; if( auth != null ) { if( auth.isNonceStale() ) { authenticateHeader = authenticateHeader + ", stale=\"true\""; } } return authenticateHeader; } catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); } } private DigestResponse toDigestResponse( Auth auth, Method m ) { DigestResponse dr = new DigestResponse( m, auth.getUser(), auth.getRealm(), auth.getNonce(), auth.getUri(), auth.getResponseDigest(), auth.getQop(), auth.getNc(), auth.getCnonce() ); return dr; } }