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.util.ArrayList;
026import java.util.List;
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.PrincipalNameType;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039
040/**
041 * A principal Name, composed of a type and N names.
042 * <pre>
043 * PrincipalName   ::= SEQUENCE {
044 *        name-type       [0] Int32,
045 *        name-string     [1] SEQUENCE OF KerberosString
046 * }
047 * </pre>
048 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
049 */
050public class PrincipalName implements Asn1Object
051{
052    /** The logger */
053    private static final Logger LOG = LoggerFactory.getLogger( PrincipalName.class );
054
055    /** Speedup for logs */
056    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
057
058    /** The type for this principal */
059    private PrincipalNameType nameType;
060
061    /** The principal name - we may have more than one - */
062    private List<String> nameString = new ArrayList<>();
063
064    /** The principal name as a byte[], for encoding purpose */
065    private List<byte[]> nameBytes;
066
067    // Storage for computed lengths
068    private int principalNameSeqLength;
069    private int principalTypeTagLength;
070    private int principalTypeLength;
071    private int principalStringsTagLength;
072    private int principalStringsSeqLength;
073
074
075    /**
076     * Creates a new empty instance of PrincipalName.
077     */
078    public PrincipalName()
079    {
080    }
081
082
083    /**
084     * Returns the type of the {@link PrincipalName}.
085     *
086     * @return The type of the {@link PrincipalName}.
087     */
088    public PrincipalNameType getNameType()
089    {
090        return nameType;
091    }
092
093
094    /** 
095     * Set the Principal name Type
096     * @param nameType the Principal name Type
097     */
098    public void setNameType( PrincipalNameType nameType )
099    {
100        this.nameType = nameType;
101    }
102
103
104    /**
105     * @return A String representing the principal names as a String 
106     */
107    public String getNameString()
108    {
109        if ( ( nameString == null ) || nameString.isEmpty() )
110        {
111            return "";
112        }
113        else
114        {
115            StringBuilder sb = new StringBuilder();
116            boolean isFirst = true;
117
118            for ( String name : nameString )
119            {
120                if ( isFirst )
121                {
122                    isFirst = false;
123                }
124                else
125                {
126                    sb.append( '/' );
127                }
128
129                sb.append( name );
130            }
131
132            return sb.toString();
133        }
134    }
135
136
137    /**
138     * Add a new name to the PrincipalName
139     * @param name The name to add
140     */
141    public void addName( String name )
142    {
143        if ( nameString == null )
144        {
145            nameString = new ArrayList<>();
146        }
147
148        nameString.add( name );
149    }
150
151
152    /**
153     * Compute the PrincipalName length
154     * <pre>
155     * PrincipalName :
156     * 
157     * 0x30 L1 PrincipalName sequence
158     *  |
159     *  +--&gt; 0xA1 L2 name-type tag
160     *  |     |
161     *  |     +--&gt; 0x02 L2-1 addressType (int)
162     *  |
163     *  +--&gt; 0xA2 L3 name-string tag
164     *        |
165     *        +--&gt; 0x30 L3-1 name-string (SEQUENCE OF KerberosString)
166     *              |
167     *              +--&gt; 0x1B L4[1] value (KerberosString)
168     *              |
169     *              +--&gt; 0x1B L4[2] value (KerberosString)
170     *              |
171     *              ...
172     *              |
173     *              +--&gt; 0x1B L4[n] value (KerberosString)
174     * </pre>
175     */
176    public int computeLength()
177    {
178        // The principalName can't be empty.
179        principalTypeLength = BerValue.getNbBytes( nameType.getValue() );
180        principalTypeTagLength = 1 + 1 + principalTypeLength;
181
182        principalNameSeqLength = 1 + TLV.getNbBytes( principalTypeTagLength ) + principalTypeTagLength;
183
184        // Compute the keyValue
185        if ( ( nameString == null ) || nameString.isEmpty() )
186        {
187            principalStringsSeqLength = 0;
188        }
189        else
190        {
191            principalStringsSeqLength = 0;
192            nameBytes = new ArrayList<>( nameString.size() );
193
194            for ( String name : nameString )
195            {
196                if ( name != null )
197                {
198                    byte[] bytes = Strings.getBytesUtf8( name );
199                    nameBytes.add( bytes );
200                    principalStringsSeqLength += 1 + TLV.getNbBytes( bytes.length ) + bytes.length;
201                }
202                else
203                {
204                    nameBytes.add( Strings.EMPTY_BYTES );
205                    principalStringsSeqLength += 1 + 1;
206                }
207            }
208        }
209
210        principalStringsTagLength = 1 + TLV.getNbBytes( principalStringsSeqLength ) + principalStringsSeqLength;
211        principalNameSeqLength += 1 + TLV.getNbBytes( principalStringsTagLength ) + principalStringsTagLength;
212
213        // Compute the whole sequence length
214        return 1 + TLV.getNbBytes( principalNameSeqLength ) + principalNameSeqLength;
215    }
216
217
218    /**
219     * Encode the PrincipalName message to a PDU. 
220     * <pre>
221     * PrincipalName :
222     * 
223     * 0x30 LL
224     *   0xA0 LL 
225     *     0x02 0x01 name-type (integer)
226     *   0xA1 LL 
227     *     0x30 LL name-string (SEQUENCE OF KerberosString)
228     *       0x1B LL name-string[1]
229     *       0x1B LL name-string[2]
230     *       ...
231     *       0x1B LL name-string[n]
232     * </pre>
233     * @param buffer The buffer where to put the PDU. It should have been allocated
234     * before, with the right size.
235     * @return The constructed PDU.
236     */
237    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
238    {
239        if ( buffer == null )
240        {
241            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
242        }
243
244        try
245        {
246            // The PrincipalName SEQ Tag
247            buffer.put( UniversalTag.SEQUENCE.getValue() );
248            buffer.put( TLV.getBytes( principalNameSeqLength ) );
249
250            // The name-type, first the tag, then the value
251            buffer.put( ( byte ) 0xA0 );
252            buffer.put( TLV.getBytes( principalTypeTagLength ) );
253            BerValue.encode( buffer, nameType.getValue() );
254
255            // The name-string tag
256            buffer.put( ( byte ) 0xA1 );
257            buffer.put( TLV.getBytes( principalStringsTagLength ) );
258
259            // The name-string sequence
260            buffer.put( UniversalTag.SEQUENCE.getValue() );
261
262            if ( ( nameString == null ) || nameString.isEmpty() )
263            {
264                buffer.put( ( byte ) 0x00 );
265            }
266            else
267            {
268                buffer.put( TLV.getBytes( principalStringsSeqLength ) );
269
270                // The kerberosStrings
271                for ( byte[] name : nameBytes )
272                {
273                    buffer.put( UniversalTag.GENERAL_STRING.getValue() );
274
275                    if ( ( name == null ) || ( name.length == 0 ) )
276                    {
277                        buffer.put( ( byte ) 0x00 );
278                    }
279                    else
280                    {
281                        buffer.put( TLV.getBytes( name.length ) );
282                        buffer.put( name );
283                    }
284                }
285            }
286        }
287        catch ( BufferOverflowException boe )
288        {
289            LOG.error( I18n.err( I18n.ERR_146, 1 + TLV.getNbBytes( principalNameSeqLength )
290                + principalNameSeqLength, buffer.capacity() ) );
291            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
292        }
293
294        if ( IS_DEBUG )
295        {
296            LOG.debug( "PrinipalName encoding : {}", Strings.dumpBytes( buffer.array() ) );
297            LOG.debug( "PrinipalName initial value : {}", this );
298        }
299
300        return buffer;
301    }
302
303
304    /**
305     * @see Object#toString()
306     */
307    public String toString()
308    {
309        StringBuilder sb = new StringBuilder();
310
311        sb.append( "{ " );
312
313        sb.append( "name-type: " ).append( nameType.name() );
314
315        if ( ( nameString != null ) && !nameString.isEmpty() )
316        {
317            sb.append( ", name-string : <" );
318            boolean isFirst = true;
319
320            for ( String name : nameString )
321            {
322                if ( isFirst )
323                {
324                    isFirst = false;
325                }
326                else
327                {
328                    sb.append( ", " );
329                }
330
331                sb.append( '\'' ).append( name ).append( '\'' );
332            }
333
334            sb.append( ">" );
335        }
336        else
337        {
338            sb.append( " no name-string" );
339        }
340
341        sb.append( " }" );
342
343        return sb.toString();
344    }
345
346
347    @Override
348    public int hashCode()
349    {
350        final int prime = 31;
351        int result = 1;
352        result = prime * result + ( ( nameString == null ) ? 0 : nameString.hashCode() );
353        result = prime * result + ( ( nameType == null ) ? 0 : nameType.hashCode() );
354        return result;
355    }
356
357
358    /**
359     * {@inheritDoc}
360     */
361    @Override
362    public boolean equals( Object obj )
363    {
364        if ( this == obj )
365        {
366            return true;
367        }
368
369        if ( !( obj instanceof PrincipalName ) )
370        {
371            return false;
372        }
373
374        PrincipalName other = ( PrincipalName ) obj;
375
376        if ( nameString == null )
377        {
378            if ( other.nameString != null )
379            {
380                return false;
381            }
382        }
383        else if ( !nameString.equals( other.nameString ) )
384        {
385            return false;
386        }
387
388        return nameType == other.nameType;
389    }
390
391}