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     */
020    package org.apache.directory.server.kerberos.shared.messages.value;
021    
022    
023    import java.nio.BufferOverflowException;
024    import java.nio.ByteBuffer;
025    
026    import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
027    import org.apache.directory.shared.asn1.AbstractAsn1Object;
028    import org.apache.directory.shared.asn1.ber.tlv.TLV;
029    import org.apache.directory.shared.asn1.ber.tlv.UniversalTag;
030    import org.apache.directory.shared.asn1.ber.tlv.Value;
031    import org.apache.directory.shared.asn1.codec.EncoderException;
032    import org.apache.directory.shared.ldap.util.StringTools;
033    import org.slf4j.Logger;
034    import org.slf4j.LoggerFactory;
035    
036    
037    /**
038     * A structure storing an encrypted data element. The ASN.1 grammar is :
039     * 
040     * EncryptedData   ::= SEQUENCE {
041     *        etype   [0] Int32 -- EncryptionType --,
042     *        kvno    [1] UInt32 OPTIONAL,
043     *        cipher  [2] OCTET STRING -- ciphertext
044     * }
045     *
046     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047     * @version $Rev: 642496 $, $Date: 2008-03-29 05:09:22 +0200 (Sat, 29 Mar 2008) $
048     */
049    public class EncryptedData extends AbstractAsn1Object
050    {
051        /** The logger */
052        private static final Logger log = LoggerFactory.getLogger( EncryptedData.class );
053    
054        /** Speedup for logs */
055        private static final boolean IS_DEBUG = log.isDebugEnabled();
056    
057        /** The used encryption algorithm */
058        private EncryptionType eType;
059    
060        /** Version number of the key under which data is encrypted */
061        private int kvno;
062    
063        /** A flag used to tell if a kvno has been added, as the kvno is optional. */
064        private boolean hasKvno;
065    
066        /** The field containing the enciphered text */
067        private byte[] cipher;
068    
069        /** A constant used when the key is not present */
070        public static final boolean HAS_KVNO = true;
071    
072        // Storage for computed lengths
073        private transient int eTypeTagLength;
074        private transient int kvnoTagLength;
075        private transient int cipherTagLength;
076        private transient int encryptedDataSeqLength;
077    
078    
079        /**
080         * Creates a new instance of EncryptedData.
081         */
082        public EncryptedData()
083        {
084            hasKvno = !HAS_KVNO;
085        }
086        
087        /**
088         * Creates a new instance of EncryptedData.
089         *
090         * @param eType The encription algorithm
091         * @param kvno The key version
092         * @param cipher the encrypted text
093         */
094        public EncryptedData( EncryptionType eType, int kvno, byte[] cipher )
095        {
096            this.eType = eType;
097            this.hasKvno = kvno > 0;
098            this.kvno = kvno;
099            this.cipher = cipher;
100        }
101    
102    
103        /**
104         * Creates a new instance of EncryptedData.
105         *
106         * @param eType The encription algorithm
107         * @param cipher the encrypted text
108         */
109        public EncryptedData( EncryptionType eType, byte[] cipher )
110        {
111            this.eType = eType;
112            this.hasKvno = !HAS_KVNO;
113            kvno = -1;
114            this.cipher = cipher;
115        }
116    
117    
118        /**
119         * Returns the {@link EncryptionType}.
120         *
121         * @return The {@link EncryptionType}.
122         */
123        public EncryptionType getEType()
124        {
125            return eType;
126        }
127    
128    
129        /**
130         * Set the EncryptionType
131         * @param eType the EncryptionType
132         */
133        public void setEType( EncryptionType eType )
134        {
135            this.eType = eType;
136        }
137    
138        /**
139         * Returns the key version.
140         *
141         * @return The key version.
142         */
143        public int getKvno()
144        {
145            return hasKvno ? kvno : -1;
146        }
147    
148        /**
149         * Set the key version
150         * @param kvno The key version
151         */
152        public void setKvno( int kvno )
153        {
154            this.kvno = kvno;
155        }
156    
157        /**
158         * Tells if there is a key version.
159         *
160         * @return <code>true</code> if there is a key version.
161         */
162        public boolean hasKvno()
163        {
164            return hasKvno;
165        }
166    
167    
168        /**
169         * Returns the raw cipher text.
170         *
171         * @return The raw cipher text.
172         */
173        public byte[] getCipher()
174        {
175            return cipher;
176        }
177    
178        /**
179         * Set the cipher text
180         * @param cipher The cipher text
181         */
182        public void setCipher( byte[] cipher )
183        {
184            this.cipher = cipher;
185        }
186        
187    
188        /**
189         * Compute the EncryptedData length
190         * 
191         * EncryptedData :
192         * 
193         * 0x30 L1 EncryptedData sequence
194         *  |
195         *  +--> 0xA1 L2 etype tag
196         *  |     |
197         *  |     +--> 0x02 L2-1 etype (int)
198         *  |
199         *  +--> [0xA2 L3 kvno tag
200         *  |     |
201         *  |     +--> 0x30 L3-1 kvno (int)] (optional)
202         *  |
203         *  +--> 0xA2 L4 cipher tag
204         *        |
205         *        +--> 0x04 L4-1 cipher (OCTET STRING)
206         */
207        public int computeLength()
208        {
209            encryptedDataSeqLength = 0;
210    
211            // Compute the encryption Type length
212            int eTypeLength = Value.getNbBytes( eType.getOrdinal() );
213            eTypeTagLength = 1 + TLV.getNbBytes( eTypeLength ) + eTypeLength;
214            encryptedDataSeqLength = 1 + TLV.getNbBytes( eTypeTagLength ) + eTypeTagLength; 
215    
216    
217            // Compute the kvno length if any
218            if ( hasKvno )
219            {
220                int kvnoLength = Value.getNbBytes( kvno );
221                kvnoTagLength = 1 + TLV.getNbBytes( kvnoLength ) + kvnoLength;
222                encryptedDataSeqLength += 1 + TLV.getNbBytes( kvnoTagLength ) + kvnoTagLength;
223            }
224            else
225            {
226                kvnoTagLength = 0;
227            }
228    
229            // Compute the cipher
230            if ( ( cipher == null ) || ( cipher.length == 0 ) )
231            {
232                cipherTagLength = 1 + 1;
233            }
234            else
235            {
236                cipherTagLength = 1 + TLV.getNbBytes( cipher.length ) + cipher.length;
237            }
238    
239            encryptedDataSeqLength += 1 + TLV.getNbBytes( cipherTagLength ) + cipherTagLength;
240    
241            // Compute the whole sequence length
242            return 1 + TLV.getNbBytes( encryptedDataSeqLength ) + encryptedDataSeqLength;
243        }
244    
245    
246        /**
247         * Encode the EncryptedData message to a PDU. 
248         * 
249         * EncryptedData :
250         * 
251         * 0x30 LL
252         *   0xA0 LL 
253         *     0x02 0x01 etype (integer)
254         *   [0xA1 LL 
255         *     0x02 0x01 kvno (integer)] (optional)
256         *   0xA2 LL 
257         *     0x04 LL cipher (OCTET STRING)
258         * 
259         * @param buffer The buffer where to put the PDU. It should have been allocated
260         * before, with the right size.
261         * @return The constructed PDU.
262         */
263        public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
264        {
265            if ( buffer == null )
266            {
267                throw new EncoderException( "Cannot put a PDU in a null buffer !" );
268            }
269    
270            try
271            {
272                // The EncryptedData SEQ Tag
273                buffer.put( UniversalTag.SEQUENCE_TAG );
274                buffer.put( TLV.getBytes( encryptedDataSeqLength ) );
275    
276                // The etype, first the tag, then the value
277                buffer.put( ( byte ) 0xA0 );
278                buffer.put( TLV.getBytes( eTypeTagLength ) );
279    
280                Value.encode( buffer, eType.getOrdinal() );
281    
282                // The kvno, if any, first the tag, then the value
283                if ( hasKvno )
284                {
285                    buffer.put( ( byte ) 0xA1 );
286                    buffer.put( TLV.getBytes( kvnoTagLength ) );
287    
288                    Value.encode( buffer, kvno );
289                }
290    
291                // The cipher tag
292                buffer.put( ( byte ) 0xA2 );
293                buffer.put( TLV.getBytes( cipherTagLength ) );
294                Value.encode( buffer, cipher );
295            }
296            catch ( BufferOverflowException boe )
297            {
298                log.error(
299                    "Cannot encode the EncryptedData object, the PDU size is {} when only {} bytes has been allocated", 1
300                        + TLV.getNbBytes( encryptedDataSeqLength ) + encryptedDataSeqLength, buffer.capacity() );
301                throw new EncoderException( "The PDU buffer size is too small !" );
302            }
303    
304            if ( IS_DEBUG )
305            {
306                log.debug( "EncryptedData encoding : {}", StringTools.dumpBytes( buffer.array() ) );
307                log.debug( "EncryptedData initial value : {}", toString() );
308            }
309    
310            return buffer;
311        }
312    
313    
314        /**
315         * @see Object#toString()
316         */
317        public String toString()
318        {
319            StringBuilder sb = new StringBuilder();
320    
321            sb.append( "EncryptedData : {\n" );
322            sb.append( "    etype: " ).append( eType ).append( '\n' );
323    
324            if ( hasKvno )
325            {
326                sb.append( "    kvno: " ).append( kvno ).append( '\n' );
327            }
328    
329            sb.append( "    cipher: " ).append( StringTools.dumpBytes( cipher ) ).append( "\n}\n" );
330    
331            return sb.toString();
332        }
333    }