Project: AirReceiver
/*
 * This file is part of AirReceiver. 
 * 
 * AirReceiver is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 
 * AirReceiver is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * GNU General Public License for more details. 
 
 * You should have received a copy of the GNU General Public License 
 * along with AirReceiver.  If not, see <http://www.gnu.org/licenses/>. 
 */
 
package org.phlo.AirReceiver; 
 
import java.net.*; 
import java.nio.ByteBuffer; 
 
import javax.crypto.*; 
 
import org.jboss.netty.channel.*; 
import org.jboss.netty.handler.codec.http.*; 
 
/**
 * Adds an {@code Apple-Response} header to a response if the request contain 
 * an {@code Apple-Request} header. 
 */
 
public class RaopRtspChallengeResponseHandler extends SimpleChannelHandler 
 private static final String HeaderChallenge = "Apple-Challenge"
 private static final String HeaderSignature = "Apple-Response"
 
 private final byte[] m_hwAddress; 
 private final Cipher m_rsaPkCS1PaddingCipher = AirTunesCrytography.getCipher("RSA/None/PKCS1Padding"); 
 
 private byte[] m_challenge; 
 private InetAddress m_localAddress; 
 
 public RaopRtspChallengeResponseHandler(final byte[] hwAddress) { 
  assert hwAddress.length == 6; 
 
  m_hwAddress = hwAddress; 
 
 
 @Override 
 public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent evt) 
  throws Exception 
 
  final HttpRequest req = (HttpRequest)evt.getMessage(); 
 
  synchronized(this) { 
   if (req.containsHeader(HeaderChallenge)) { 
    /* The challenge is sent without padding! */ 
    final byte[] challenge = Base64.decodeUnpadded(req.getHeader(HeaderChallenge)); 
 
    /* Verify that we got 16 bytes */ 
    if (challenge.length != 16
     throw new ProtocolException("Invalid Apple-Challenge header, " + challenge.length + " instead of 16 bytes"); 
 
    /* Remember challenge and local address.
     * Both are required to compute the response 
     */
 
    m_challenge = challenge; 
    m_localAddress = ((InetSocketAddress)ctx.getChannel().getLocalAddress()).getAddress(); 
   
   else { 
    /* Forget last challenge */ 
    m_challenge = null
    m_localAddress = null
   
  
 
  super.messageReceived(ctx, evt); 
 
 
 @Override 
 public void writeRequested(final ChannelHandlerContext ctx, final MessageEvent evt) 
  throws Exception 
 
  final HttpResponse resp = (HttpResponse)evt.getMessage(); 
 
  synchronized(this) { 
   if (m_challenge != null) { 
    try { 
     /* Get appropriate response to challenge and
      * add to the response base-64 encoded. XXX 
      */
 
     final String sig = Base64.encodePadded(getSignature()); 
 
     resp.setHeader(HeaderSignature, sig); 
    
    finally { 
     /* Forget last challenge */ 
     m_challenge = null
     m_localAddress = null
    
   
  
 
  super.writeRequested(ctx, evt); 
 
 
 private byte[] getSignature() { 
  final ByteBuffer sigData = ByteBuffer.allocate(16 /* challenge */ + 16 /* ipv6 address */ + 6 /* hw address*/); 
 
  sigData.put(m_challenge); 
  sigData.put(m_localAddress.getAddress()); 
  sigData.put(m_hwAddress); 
  while (sigData.hasRemaining()) 
   sigData.put((byte)0); 
 
  try { 
   m_rsaPkCS1PaddingCipher.init(Cipher.ENCRYPT_MODE, AirTunesCrytography.PrivateKey); 
   return m_rsaPkCS1PaddingCipher.doFinal(sigData.array()); 
  
  catch (final Exception e) { 
   throw new RuntimeException("Unable to sign response", e); 
  
 
}