001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.shared.kerberos.components;
021
022
023import java.nio.BufferOverflowException;
024import java.nio.ByteBuffer;
025import java.security.MessageDigest;
026import java.util.Arrays;
027
028import org.apache.directory.api.asn1.Asn1Object;
029import org.apache.directory.api.asn1.EncoderException;
030import org.apache.directory.api.asn1.ber.tlv.BerValue;
031import org.apache.directory.api.asn1.ber.tlv.TLV;
032import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
033import org.apache.directory.api.util.Strings;
034import org.apache.directory.server.i18n.I18n;
035import org.apache.directory.shared.kerberos.KerberosConstants;
036import org.apache.directory.shared.kerberos.crypto.checksum.ChecksumType;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040
041/**
042 * The Checksum structure is used to store a checksum associated to a type.
043 * 
044 * The ASN.1 grammar is :
045 * <pre>
046 * Checksum        ::= SEQUENCE {
047 *       cksumtype       [0] Int32,
048 *       checksum        [1] OCTET STRING
049 * }
050 * </pre>
051 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
052 */
053public class Checksum implements Asn1Object
054{
055    /** The logger */
056    private static final Logger log = LoggerFactory.getLogger( Checksum.class );
057
058    /** Speedup for logs */
059    private static final boolean IS_DEBUG = log.isDebugEnabled();
060
061    /** The checksum type used */
062    private ChecksumType cksumtype;
063
064    /** The byte array containing the checksum */
065    private byte[] checksum;
066
067    // Storage for computed lengths
068    private int checksumTypeLength;
069    private int checksumBytesLength;
070    private int checksumLength;
071
072
073    /**
074     * Creates a new instance of Checksum.
075     */
076    public Checksum()
077    {
078    }
079
080
081    /**
082     * Creates a new instance of Checksum.
083     *
084     * @param cksumtype The checksum type used
085     * @param checksum The checksum value
086     */
087    public Checksum( ChecksumType cksumtype, byte[] checksum )
088    {
089        this.cksumtype = cksumtype;
090        this.checksum = checksum;
091    }
092
093
094    /**
095     * {@inheritDoc}
096     */
097    @Override
098    public int hashCode()
099    {
100        int hash = 37;
101        hash = hash * 17 + cksumtype.hashCode();
102        hash = hash * 17 + Arrays.hashCode( checksum );
103
104        return hash;
105    }
106
107
108    /**
109     * @see Object#equals(Object)
110     */
111    @Override
112    public boolean equals( Object o )
113    {
114        if ( this == o )
115        {
116            return true;
117        }
118
119        if ( !( o instanceof Checksum ) )
120        {
121            return false;
122        }
123
124        Checksum that = ( Checksum ) o;
125
126        return ( cksumtype == that.cksumtype ) && ( MessageDigest.isEqual( checksum, that.checksum ) );
127    }
128
129
130    /**
131     * Returns the checksum value.
132     *
133     * @return The checksum value.
134     */
135    public byte[] getChecksumValue()
136    {
137        return checksum;
138    }
139
140
141    /**
142     * Set the checksum Value.
143     *
144     * @param checksum The checksum value
145     */
146    public void setChecksumValue( byte[] checksum )
147    {
148        this.checksum = checksum;
149    }
150
151
152    /**
153     * Returns the {@link ChecksumType}.
154     *
155     * @return The {@link ChecksumType}.
156     */
157    public ChecksumType getChecksumType()
158    {
159        return cksumtype;
160    }
161
162
163    /**
164     * Set the {@link ChecksumType}.
165     *
166     * @param cksumType The checksum algorithm used
167     */
168    public void setChecksumType( ChecksumType cksumType )
169    {
170        this.cksumtype = cksumType;
171    }
172
173
174    /**
175     * Compute the checksum length
176     * <pre>
177     * Checksum :
178     * 
179     * 0x30 L1 checksum sequence
180     *  |
181     *  +--&gt; 0xA0 L2 cksumtype tag
182     *  |     |
183     *  |     +--&gt; 0x02 L2-1 cksumtype (int)
184     *  |
185     *  +--&gt; 0xA1 L3 checksum tag
186     *        |
187     *        +--&gt; 0x04 L3-1 checksum (OCTET STRING)
188     *        
189     *  where L1 = L2 + lenght(0xA0) + length(L2) +
190     *             L3 + lenght(0xA1) + length(L3) 
191     *  and
192     *  L2 = L2-1 + length(0x02) + length( L2-1) 
193     *  L3 = L3-1 + length(0x04) + length( L3-1) 
194     *  </pre>
195     */
196    public int computeLength()
197    {
198        // Compute the checksulType. The Length will always be contained in 1 byte
199        checksumTypeLength = 1 + 1 + BerValue.getNbBytes( cksumtype.getValue() );
200        checksumLength = 1 + TLV.getNbBytes( checksumTypeLength ) + checksumTypeLength;
201
202        // Compute the checksum Value
203        if ( checksum == null )
204        {
205            checksumBytesLength = 1 + 1;
206        }
207        else
208        {
209            checksumBytesLength = 1 + TLV.getNbBytes( checksum.length ) + checksum.length;
210        }
211
212        checksumLength += 1 + TLV.getNbBytes( checksumBytesLength ) + checksumBytesLength;
213
214        // Compute the whole sequence length
215        return 1 + TLV.getNbBytes( checksumLength ) + checksumLength;
216    }
217
218
219    /**
220     * Encode the Checksum message to a PDU. 
221     * 
222     * <pre>
223     * Checksum :
224     * 
225     * 0x30 LL
226     *   0xA0 LL 
227     *     0x02 0x01 cksumtype
228     *   0xA1 LL 
229     *     0x04 LL Checksum
230     * </pre>
231     * @param buffer The buffer where to put the PDU. It should have been allocated
232     * before, with the right size.
233     * @return The constructed PDU.
234     */
235    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
236    {
237        if ( buffer == null )
238        {
239            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
240        }
241
242        try
243        {
244            // The Checksum SEQ Tag
245            buffer.put( UniversalTag.SEQUENCE.getValue() );
246            buffer.put( TLV.getBytes( checksumLength ) );
247
248            // The cksumtype, first the tag, then the value
249            buffer.put( ( byte ) KerberosConstants.CHECKSUM_TYPE_TAG );
250            buffer.put( TLV.getBytes( checksumTypeLength ) );
251            BerValue.encode( buffer, cksumtype.getValue() );
252
253            // The checksum, first the tag, then the value
254            buffer.put( ( byte ) KerberosConstants.CHECKSUM_CHECKSUM_TAG );
255            buffer.put( TLV.getBytes( checksumBytesLength ) );
256            BerValue.encode( buffer, checksum );
257        }
258        catch ( BufferOverflowException boe )
259        {
260            log.error( I18n.err( I18n.ERR_140, 1 + TLV.getNbBytes( checksumLength ) + checksumLength,
261                buffer.capacity() ) );
262            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
263        }
264
265        if ( IS_DEBUG )
266        {
267            log.debug( "Checksum encoding : {}", Strings.dumpBytes( buffer.array() ) );
268            log.debug( "Checksum initial value : {}", this );
269        }
270
271        return buffer;
272    }
273
274
275    /**
276     * @see Object#toString()
277     */
278    public String toString()
279    {
280        return toString( "" );
281    }
282
283
284    /**
285     * @see Object#toString()
286     */
287    public String toString( String tabs )
288    {
289        StringBuilder sb = new StringBuilder();
290
291        sb.append( tabs ).append( "Checksum : {\n" );
292        sb.append( tabs ).append( "    cksumtype: " ).append( cksumtype ).append( '\n' );
293
294        if ( checksum != null )
295        {
296            sb.append( tabs + "    checksum:" ).append( Strings.dumpBytes( checksum ) ).append( '\n' );
297        }
298
299        sb.append( tabs + "}\n" );
300
301        return sb.toString();
302    }
303}