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.kerberos.credentials.cache;
021
022
023import java.io.DataInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.nio.charset.StandardCharsets;
027import java.util.ArrayList;
028import java.util.List;
029
030import org.apache.directory.shared.kerberos.KerberosTime;
031import org.apache.directory.shared.kerberos.codec.KerberosDecoder;
032import org.apache.directory.shared.kerberos.codec.types.AuthorizationType;
033import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
034import org.apache.directory.shared.kerberos.codec.types.HostAddrType;
035import org.apache.directory.shared.kerberos.components.AuthorizationData;
036import org.apache.directory.shared.kerberos.components.AuthorizationDataEntry;
037import org.apache.directory.shared.kerberos.components.EncryptionKey;
038import org.apache.directory.shared.kerberos.components.HostAddress;
039import org.apache.directory.shared.kerberos.components.HostAddresses;
040import org.apache.directory.shared.kerberos.components.PrincipalName;
041import org.apache.directory.shared.kerberos.flags.TicketFlags;
042
043
044/**
045 * Reading credentials cache according to FCC format by reference the following
046 * https://www.gnu.org/software/shishi/manual/html_node/The-Credential-Cache-Binary-File-Format.html
047 * 
048 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
049 */
050public class CacheInputStream extends DataInputStream
051{
052    public CacheInputStream( InputStream in )
053    {
054        super( in );
055    }
056
057
058    public void read( CredentialsCache cache ) throws IOException
059    {
060        int version;
061        List<Tag> tags;
062        PrincipalName principal;
063        Credentials cred;
064
065        version = readVersion();
066        cache.setVersion( version );
067
068        if ( version == CredentialsCacheConstants.FCC_FVNO_4 )
069        {
070            tags = readTag();
071        }
072        else
073        {
074            tags = null;
075        }
076        cache.setTags( tags );
077
078        principal = readPrincipal( version );
079        cache.setPrimaryPrincipalName( principal );
080
081        while ( available() > 0 )
082        {
083            cred = readCredentials( version );
084            if ( cred != null )
085            {
086                cache.addCredentials( cred );
087            }
088        }
089    }
090
091
092    private int readVersion() throws IOException
093    {
094        return readShort();
095    }
096
097
098    private List<Tag> readTag() throws IOException
099    {
100        int len;
101        int tag;
102        int taglen;
103        int time;
104        int usec;
105
106        len = readShort();
107        List<Tag> tags = new ArrayList<>();
108
109        while ( len > 0 )
110        {
111            tag = readShort();
112            taglen = readShort();
113
114            if ( tag == CredentialsCacheConstants.FCC_TAG_DELTATIME )
115            {
116                time = readInt();
117                usec = readInt();
118                tags.add( new Tag( tag, time, usec ) );
119            }
120            else
121            {
122                read( new byte[taglen], 0, taglen ); // ignore unknown tag
123            }
124
125            len = len - ( 4 + taglen );
126        }
127
128        return tags;
129    }
130
131
132    private PrincipalName readPrincipal( int version ) throws IOException
133    {
134        int type;
135        int length;
136        PrincipalName pname;
137
138        if ( version == CredentialsCacheConstants.FCC_FVNO_1 )
139        {
140            type = CredentialsCacheConstants.NT_UNKNOWN;
141        }
142        else
143        {
144            type = readInt();
145        }
146
147        length = readInt();
148
149        if ( version == CredentialsCacheConstants.FCC_FVNO_1 )
150        {
151            length--;
152        }
153
154        String realm = readCountedString();
155
156        String[] result = new String[length];
157
158        for ( int i = 0; i < length; i++ )
159        {
160            result[i] = readCountedString();
161        }
162
163        pname = new PrincipalName( result, type );
164
165        if ( isRealm( realm ) )
166        {
167            pname.setRealm( realm );
168        }
169
170        return pname;
171    }
172
173
174    private String readCountedString() throws IOException
175    {
176        int namelength = readInt();
177        if ( namelength > CredentialsCacheConstants.MAXNAMELENGTH )
178        {
179            throw new IOException( "Invalid name length in principal name." );
180        }
181        byte[] bytes = new byte[namelength];
182        read( bytes, 0, bytes.length );
183
184        return new String( bytes, StandardCharsets.UTF_8 );
185    }
186
187
188    /*
189     * Domain style realm names MUST look like domain names: they consist of
190     * components separated by periods (.) and they contain neither colons
191     * (:) nor slashes (/). When establishing a new realm name based on an 
192     * internet domain name it is recommended by convention that the characters 
193     * be converted to uppercase.
194     */
195    private static boolean isRealm( String str )
196    {
197        char chr;
198        for ( int i = 0; i < str.length(); i++ )
199        {
200            chr = str.charAt( i );
201            if ( chr != '.' && chr >= 'a' )
202            {
203                return false;
204            }
205        }
206
207        return true;
208    }
209
210
211    private EncryptionKey readKey( int version ) throws IOException
212    {
213        int keyType;
214        int keyLen;
215        keyType = readShort();
216
217        if ( version == CredentialsCacheConstants.FCC_FVNO_3 )
218        {
219            readShort();
220        }
221
222        // It's not correct with "uint16_t keylen", instead "uint32_t keylen" in keyblock 
223        keyLen = readInt();
224        byte[] bytes = new byte[keyLen];
225        read( bytes, 0, bytes.length );
226
227        return new EncryptionKey( EncryptionType.getTypeByValue( keyType ), bytes );
228    }
229
230
231    private KerberosTime[] readKerberosTimes() throws IOException
232    {
233        long[] times = readTimes();
234        KerberosTime[] results = new KerberosTime[times.length];
235        KerberosTime ktime;
236
237        for ( int i = 0; i < times.length; ++i )
238        {
239            ktime = times[i] == 0 ? null : new KerberosTime( times[i] );
240            results[i] = ktime;
241        }
242
243        return results;
244    }
245
246
247    private long[] readTimes() throws IOException
248    {
249        long[] times = new long[4];
250        times[0] = ( long ) readInt() * 1000;
251        times[1] = ( long ) readInt() * 1000;
252        times[2] = ( long ) readInt() * 1000;
253        times[3] = ( long ) readInt() * 1000;
254
255        return times;
256    }
257
258
259    private boolean readskey() throws IOException
260    {
261        return read() != 0;
262    }
263
264
265    private HostAddress[] readAddr() throws IOException
266    {
267        int numAddrs;
268        int addrType;
269        int addrLength;
270        numAddrs = readInt();
271
272        if ( numAddrs > 0 )
273        {
274            HostAddress[] addrs = new HostAddress[numAddrs];
275
276            for ( int i = 0; i < numAddrs; i++ )
277            {
278                addrType = readShort();
279                addrLength = readInt();
280
281                if ( !( addrLength == 4 || addrLength == 16 ) )
282                {
283                    return null;
284                }
285
286                byte[] result = new byte[addrLength];
287
288                for ( int j = 0; j < addrLength; j++ )
289                {
290                    result[j] = readByte();
291                }
292
293                addrs[i] = new HostAddress( HostAddrType.getTypeByOrdinal( addrType ), result );
294            }
295            return addrs;
296        }
297
298        return null;
299    }
300
301
302    private AuthorizationDataEntry[] readAuth() throws IOException
303    {
304        int num;
305        int adtype;
306        int adlength;
307        num = readInt();
308
309        if ( num > 0 )
310        {
311            AuthorizationDataEntry[] auData = new AuthorizationDataEntry[num];
312            byte[] data = null;
313
314            for ( int i = 0; i < num; i++ )
315            {
316                adtype = readShort();
317                adlength = readInt();
318                data = new byte[adlength];
319                read( data, 0, data.length );
320                auData[i] = new AuthorizationDataEntry( AuthorizationType.getTypeByValue( adtype ), data );
321            }
322
323            return auData;
324        }
325
326        return null;
327    }
328
329
330    private byte[] readData() throws IOException
331    {
332        int length;
333        length = readInt();
334        if ( length == 0 )
335        {
336            return null;
337        }
338        else
339        {
340            byte[] bytes = new byte[length];
341            read( bytes, 0, length );
342            return bytes;
343        }
344    }
345
346
347    private int readFlags() throws IOException
348    {
349        int ticketFlags;
350        ticketFlags = readInt();
351        return ticketFlags;
352    }
353
354
355    private Credentials readCredentials( int version ) throws IOException
356    {
357        PrincipalName cpname = readPrincipal( version );
358        PrincipalName spname = readPrincipal( version );
359
360        EncryptionKey key = readKey( version );
361
362        KerberosTime[] times = readKerberosTimes();
363        KerberosTime authtime = times[0];
364        KerberosTime starttime = times[1];
365        KerberosTime endtime = times[2];
366        KerberosTime renewTill = times[3];
367
368        boolean skey = readskey();
369
370        int flags = readFlags();
371        TicketFlags tFlags = new TicketFlags( flags );
372        HostAddress addr[] = readAddr();
373        HostAddresses addrs = null;
374
375        if ( addr != null )
376        {
377            addrs = new HostAddresses( addr );
378        }
379
380        AuthorizationDataEntry[] auDataEntries = readAuth();
381        AuthorizationData auData = null;
382
383        if ( auDataEntries != null )
384        {
385            auData = new AuthorizationData();
386
387            for ( AuthorizationDataEntry ade : auDataEntries )
388            {
389                auData.addEntry( ade );
390            }
391        }
392
393        byte[] ticketData = readData();
394        byte[] ticketData2 = readData();
395
396        if ( version != CredentialsCacheConstants.FCC_FVNO_1 &&
397            spname.getNameType().getValue() == CredentialsCacheConstants.NT_UNKNOWN )
398        {
399            // skip krb5_ccache_conf_data/fast_avail/krbtgt/REALM@REALM in MIT KRB5
400            return null;
401        }
402
403        try
404        {
405            return new Credentials( cpname, spname, key, authtime, starttime,
406                endtime, renewTill, skey, tFlags, addrs, auData,
407                ticketData != null ? KerberosDecoder.decodeTicket( ticketData ) : null,
408                ticketData2 != null ? KerberosDecoder.decodeTicket( ticketData2 ) : null );
409        }
410        catch ( Exception e )
411        {
412            return null;
413        }
414    }
415}