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.codec.types.EncryptionType;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039
040/**
041 * A structure storing an encrypted data element. The ASN.1 grammar is :
042 * <pre>
043 * EncryptedData   ::= SEQUENCE {
044 *        etype   [0] Int32 -- EncryptionType --,
045 *        kvno    [1] UInt32 OPTIONAL,
046 *        cipher  [2] OCTET STRING -- ciphertext
047 * }
048 *</pre>
049 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
050 */
051public class EncryptedData implements Asn1Object
052{
053    /** The logger */
054    private static final Logger log = LoggerFactory.getLogger( EncryptedData.class );
055
056    /** Speedup for logs */
057    private static final boolean IS_DEBUG = log.isDebugEnabled();
058
059    /** The used encryption algorithm */
060    private EncryptionType eType;
061
062    /** Version number of the key under which data is encrypted */
063    private int kvno;
064
065    /** A flag used to tell if a kvno has been added, as the kvno is optional. */
066    private boolean hasKvno;
067
068    /** The field containing the enciphered text */
069    private byte[] cipher;
070
071    /** A constant used when the key is not present */
072    public static final boolean HAS_KVNO = true;
073
074    // Storage for computed lengths
075    private int eTypeTagLength;
076    private int kvnoTagLength;
077    private int cipherTagLength;
078    private int encryptedDataSeqLength;
079
080
081    /**
082     * Creates a new instance of EncryptedData.
083     */
084    public EncryptedData()
085    {
086        hasKvno = !HAS_KVNO;
087    }
088
089
090    /**
091     * Creates a new instance of EncryptedData.
092     *
093     * @param eType The encription algorithm
094     * @param kvno The key version
095     * @param cipher the encrypted text
096     */
097    public EncryptedData( EncryptionType eType, int kvno, byte[] cipher )
098    {
099        this.eType = eType;
100        this.hasKvno = kvno > 0;
101        this.kvno = kvno;
102        this.cipher = cipher;
103    }
104
105
106    /**
107     * Creates a new instance of EncryptedData.
108     *
109     * @param eType The encription algorithm
110     * @param cipher the encrypted text
111     */
112    public EncryptedData( EncryptionType eType, byte[] cipher )
113    {
114        this.eType = eType;
115        this.hasKvno = !HAS_KVNO;
116        kvno = -1;
117        this.cipher = cipher;
118    }
119
120
121    /**
122     * Returns the {@link EncryptionType}.
123     *
124     * @return The {@link EncryptionType}.
125     */
126    public EncryptionType getEType()
127    {
128        return eType;
129    }
130
131
132    /**
133     * Set the EncryptionType
134     * @param eType the EncryptionType
135     */
136    public void setEType( EncryptionType eType )
137    {
138        this.eType = eType;
139    }
140
141
142    /**
143     * Returns the key version.
144     *
145     * @return The key version.
146     */
147    public int getKvno()
148    {
149        return hasKvno ? kvno : -1;
150    }
151
152
153    /**
154     * Set the key version
155     * @param kvno The key version
156     */
157    public void setKvno( int kvno )
158    {
159        this.kvno = kvno;
160        hasKvno = true;
161    }
162
163
164    /**
165     * Tells if there is a key version.
166     *
167     * @return <code>true</code> if there is a key version.
168     */
169    public boolean hasKvno()
170    {
171        return hasKvno;
172    }
173
174
175    /**
176     * Returns the raw cipher text.
177     *
178     * @return The raw cipher text.
179     */
180    public byte[] getCipher()
181    {
182        return cipher;
183    }
184
185
186    /**
187     * Set the cipher text
188     * @param cipher The cipher text
189     */
190    public void setCipher( byte[] cipher )
191    {
192        this.cipher = cipher;
193    }
194
195
196    /**
197     * Compute the EncryptedData length
198     * <pre>
199     * EncryptedData :
200     * 
201     * 0x30 L1 EncryptedData sequence
202     *  |
203     *  +--&gt; 0xA1 L2 etype tag
204     *  |     |
205     *  |     +--&gt; 0x02 L2-1 etype (int)
206     *  |
207     *  +--&gt; [0xA2 L3 kvno tag
208     *  |     |
209     *  |     +--&gt; 0x30 L3-1 kvno (int)] (optional)
210     *  |
211     *  +--&gt; 0xA2 L4 cipher tag
212     *        |
213     *        +--&gt; 0x04 L4-1 cipher (OCTET STRING)
214     * </pre>
215     */
216    public int computeLength()
217    {
218        encryptedDataSeqLength = 0;
219
220        // Compute the encryption Type length
221        int eTypeLength = BerValue.getNbBytes( eType.getValue() );
222        eTypeTagLength = 1 + TLV.getNbBytes( eTypeLength ) + eTypeLength;
223        encryptedDataSeqLength = 1 + TLV.getNbBytes( eTypeTagLength ) + eTypeTagLength;
224
225        // Compute the kvno length if any
226        if ( hasKvno )
227        {
228            int kvnoLength = BerValue.getNbBytes( kvno );
229            kvnoTagLength = 1 + TLV.getNbBytes( kvnoLength ) + kvnoLength;
230            encryptedDataSeqLength += 1 + TLV.getNbBytes( kvnoTagLength ) + kvnoTagLength;
231        }
232        else
233        {
234            kvnoTagLength = 0;
235        }
236
237        // Compute the cipher
238        if ( ( cipher == null ) || ( cipher.length == 0 ) )
239        {
240            cipherTagLength = 1 + 1;
241        }
242        else
243        {
244            cipherTagLength = 1 + TLV.getNbBytes( cipher.length ) + cipher.length;
245        }
246
247        encryptedDataSeqLength += 1 + TLV.getNbBytes( cipherTagLength ) + cipherTagLength;
248
249        // Compute the whole sequence length
250        return 1 + TLV.getNbBytes( encryptedDataSeqLength ) + encryptedDataSeqLength;
251    }
252
253
254    /**
255     * Encode the EncryptedData message to a PDU. 
256     * <pre>
257     * EncryptedData :
258     * 
259     * 0x30 LL
260     *   0xA0 LL 
261     *     0x02 0x01 etype (integer)
262     *   [0xA1 LL 
263     *     0x02 0x01 kvno (integer)] (optional)
264     *   0xA2 LL 
265     *     0x04 LL cipher (OCTET STRING)
266     * </pre>
267     * @param buffer The buffer where to put the PDU. It should have been allocated
268     * before, with the right size.
269     * @return The constructed PDU.
270     */
271    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
272    {
273        if ( buffer == null )
274        {
275            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
276        }
277
278        try
279        {
280            // The EncryptedData SEQ Tag
281            buffer.put( UniversalTag.SEQUENCE.getValue() );
282            buffer.put( TLV.getBytes( encryptedDataSeqLength ) );
283
284            // The etype, first the tag, then the value
285            buffer.put( ( byte ) 0xA0 );
286            buffer.put( TLV.getBytes( eTypeTagLength ) );
287
288            BerValue.encode( buffer, eType.getValue() );
289
290            // The kvno, if any, first the tag, then the value
291            if ( hasKvno )
292            {
293                buffer.put( ( byte ) 0xA1 );
294                buffer.put( TLV.getBytes( kvnoTagLength ) );
295
296                BerValue.encode( buffer, kvno );
297            }
298
299            // The cipher tag
300            buffer.put( ( byte ) 0xA2 );
301            buffer.put( TLV.getBytes( cipherTagLength ) );
302            BerValue.encode( buffer, cipher );
303        }
304        catch ( BufferOverflowException boe )
305        {
306            log.error( I18n.err( I18n.ERR_141, 1 + TLV.getNbBytes( encryptedDataSeqLength )
307                + encryptedDataSeqLength, buffer.capacity() ) );
308            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
309        }
310
311        if ( IS_DEBUG )
312        {
313            log.debug( "EncryptedData encoding : {}", Strings.dumpBytes( buffer.array() ) );
314            log.debug( "EncryptedData initial value : {}", this );
315        }
316
317        return buffer;
318    }
319
320
321    /**
322     * {@inheritDoc}
323     */
324    @Override
325    public int hashCode()
326    {
327        final int prime = 31;
328        int result = 1;
329        result = prime * result + Arrays.hashCode( cipher );
330        result = prime * result + ( ( eType == null ) ? 0 : eType.hashCode() );
331        result = prime * result + kvno;
332        return result;
333    }
334
335
336    /**
337     * {@inheritDoc}
338     */
339    @Override
340    public boolean equals( Object obj )
341    {
342        if ( this == obj )
343        {
344            return true;
345        }
346
347        if ( !( obj instanceof EncryptedData ) )
348        {
349            return false;
350        }
351
352        EncryptedData other = ( EncryptedData ) obj;
353
354        if ( !MessageDigest.isEqual( cipher, other.cipher ) )
355        {
356            return false;
357        }
358
359        if ( eType != other.eType )
360        {
361            return false;
362        }
363
364        return kvno == other.kvno;
365    }
366
367
368    /**
369     * @see Object#toString()
370     */
371    public String toString()
372    {
373        return toString( "" );
374    }
375
376
377    /**
378     * @see Object#toString()
379     */
380    public String toString( String tabs )
381    {
382        StringBuilder sb = new StringBuilder();
383
384        sb.append( tabs ).append( "EncryptedData : {\n" );
385        sb.append( tabs ).append( "    etype: " ).append( eType ).append( '\n' );
386
387        if ( hasKvno )
388        {
389            sb.append( tabs ).append( "    kvno: " ).append( kvno ).append( '\n' );
390        }
391
392        sb.append( tabs ).append( "    cipher: " ).append( Strings.dumpBytes( cipher ) ).append( "\n}\n" );
393
394        return sb.toString();
395    }
396}