View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.kerberos.shared.keytab;
21  
22  
23  import java.io.IOException;
24  import java.io.UnsupportedEncodingException;
25  import java.nio.ByteBuffer;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.directory.shared.kerberos.KerberosTime;
30  import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
31  import org.apache.directory.shared.kerberos.components.EncryptionKey;
32  
33  
34  /**
35   * Decode a {@link ByteBuffer} into keytab fields.
36   *
37   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
38   */
39  class KeytabDecoder
40  {
41      /**
42       * Read the keytab 16-bit file format version.  This
43       * keytab reader currently only supports version 5.2.
44       */
45      byte[] getKeytabVersion( ByteBuffer buffer )
46      {
47          byte[] version = new byte[2];
48          buffer.get( version );
49  
50          return version;
51      }
52  
53  
54      /**
55       * Read keytab entries until there is no remaining data
56       * in the buffer.
57       *
58       * @param buffer
59       * @return The keytab entries.
60       */
61      List<KeytabEntry> getKeytabEntries( ByteBuffer buffer ) throws IOException
62      {
63          List<KeytabEntry> entries = new ArrayList<>();
64  
65          while ( buffer.remaining() > 0 )
66          {
67              int size = buffer.getInt();
68              
69              if ( ( size < 0 ) || ( size > buffer.capacity() ) )
70              {
71                  throw new IOException( "Invalid size for the keytab entry" );
72              }
73              
74              byte[] entry = new byte[size];
75  
76              buffer.get( entry );
77              entries.add( getKeytabEntry( ByteBuffer.wrap( entry ) ) );
78          }
79  
80          return entries;
81      }
82  
83  
84      /**
85       * Reads off a "keytab entry," which consists of a principal name,
86       * principal type, key version number, and key material.
87       */
88      private KeytabEntry getKeytabEntry( ByteBuffer buffer ) throws IOException
89      {
90          String principalName = getPrincipalName( buffer );
91  
92          int principalType = buffer.getInt();
93  
94          long time = buffer.getInt();
95          KerberosTimekerberos/KerberosTime.html#KerberosTime">KerberosTime timeStamp = new KerberosTime( time * 1000 );
96  
97          byte keyVersion = buffer.get();
98  
99          EncryptionKey key = getKeyBlock( buffer );
100 
101         return new KeytabEntry( principalName, principalType, timeStamp, keyVersion, key );
102     }
103 
104 
105     /**
106      * Reads off a principal name.
107      *
108      * @param buffer
109      * @return The principal name.
110      */
111     private String getPrincipalName( ByteBuffer buffer ) throws IOException
112     {
113         int count = buffer.getShort();
114 
115         // decrement for v1
116         String realm = getCountedString( buffer );
117 
118         StringBuilder principalNameBuffer = new StringBuilder();
119 
120         for ( int i = 0; i < count; i++ )
121         {
122             String nameComponent = getCountedString( buffer );
123 
124             principalNameBuffer.append( nameComponent );
125 
126             if ( i < count - 1 )
127             {
128                 principalNameBuffer.append( "/" );
129             }
130         }
131 
132         principalNameBuffer.append( "@" ).append( realm );
133 
134         return principalNameBuffer.toString();
135     }
136 
137 
138     /**
139      * Read off a 16-bit encryption type and symmetric key material.
140      */
141     private EncryptionKey getKeyBlock( ByteBuffer buffer ) throws IOException
142     {
143         int type = buffer.getShort();
144         byte[] keyblock = getCountedBytes( buffer );
145 
146         EncryptionType encryptionType = EncryptionType.getTypeByValue( type );
147 
148         return new EncryptionKey( encryptionType, keyblock );
149     }
150 
151 
152     /**
153      * Use a prefixed 16-bit length to read off a String.  Realm and name
154      * components are ASCII encoded text with no zero terminator.
155      */
156     private String getCountedString( ByteBuffer buffer ) throws IOException
157     {
158         int length = buffer.getShort();
159         
160         if ( ( length < 0 ) || ( length > buffer.capacity() ) )
161         {
162             throw new IOException( "Invalid size for the keytab entry" );
163         }
164 
165         byte[] data = new byte[length];
166         buffer.get( data );
167 
168         try
169         {
170             return new String( data, "ASCII" );
171         }
172         catch ( UnsupportedEncodingException uee )
173         {
174             // Should never happen for ASCII
175             return "";
176         }
177     }
178 
179 
180     /**
181      * Use a prefixed 16-bit length to read off raw bytes.
182      */
183     private byte[] getCountedBytes( ByteBuffer buffer ) throws IOException
184     {
185         int length = buffer.getShort();
186         
187         if ( ( length < 0 ) || ( length > buffer.capacity() ) )
188         {
189             throw new IOException( "Invalid size for the keytab entry" );
190         }
191 
192         byte[] data = new byte[length];
193         buffer.get( data );
194 
195         return data;
196     }
197 }