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.server.kerberos.shared.keytab; 021 022 023import java.io.UnsupportedEncodingException; 024import java.nio.ByteBuffer; 025import java.util.ArrayList; 026import java.util.List; 027 028import org.apache.directory.shared.kerberos.components.EncryptionKey; 029 030 031/** 032 * Encode keytab fields into a {@link ByteBuffer}. 033 * 034 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 035 */ 036public class KeytabEncoder 037{ 038 /** 039 * Tells if the keytabCersion is 0x0501 or 0x0502 040 */ 041 private short getKeytabVersion( byte[] version ) 042 { 043 if ( ( version != null ) && ( version.length == 2 ) && ( version[0] == 0x05 ) ) 044 { 045 switch ( version[1] ) 046 { 047 case 0x01: 048 return Keytab.VERSION_0X501; 049 050 case 0x02: 051 return Keytab.VERSION_0X502; 052 053 default: 054 return -1; 055 } 056 } 057 058 return -1; 059 } 060 061 062 /** 063 * Write the keytab version and entries into a {@link ByteBuffer}. 064 * 065 * @param keytabVersion 066 * @param entries 067 * @return The ByteBuffer. 068 */ 069 public ByteBuffer write( byte[] keytabVersion, List<KeytabEntry> entries ) 070 { 071 List<ByteBuffer> keytabEntryBuffers = new ArrayList<>(); 072 short version = getKeytabVersion( keytabVersion ); 073 074 int buffersSize = encodeKeytabEntries( keytabEntryBuffers, version, entries ); 075 076 ByteBuffer buffer = ByteBuffer.allocate( 077 keytabVersion.length + buffersSize ); 078 079 // The keytab version (0x0502 or 0x5001) 080 buffer.put( keytabVersion ); 081 082 for ( ByteBuffer keytabEntryBuffer : keytabEntryBuffers ) 083 { 084 // The buffer 085 buffer.put( keytabEntryBuffer ); 086 } 087 088 buffer.flip(); 089 090 return buffer; 091 } 092 093 094 /** 095 * Encode the keytab entries. Each entry stores : 096 * - the size 097 * - the principal name 098 * - the type (int, 4 bytes) 099 * - the timestamp (int, 4 bytes) 100 * - the key version (1 byte) 101 * - the key 102 * 103 * @param buffer 104 * @param entries 105 */ 106 private int encodeKeytabEntries( List<ByteBuffer> buffers, short version, List<KeytabEntry> entries ) 107 { 108 int size = 0; 109 110 for ( KeytabEntry keytabEntry : entries ) 111 { 112 ByteBuffer entryBuffer = encodeKeytabEntry( version, keytabEntry ); 113 114 buffers.add( entryBuffer ); 115 116 // The buffer size 117 size += entryBuffer.limit(); 118 } 119 120 return size; 121 } 122 123 124 /** 125 * Encode a "keytab entry," which consists of a principal name, 126 * principal type, key version number, and key material. 127 */ 128 private ByteBuffer encodeKeytabEntry( short version, KeytabEntry entry ) 129 { 130 // Compute the principalName encoding 131 ByteBuffer principalNameBuffer = encodePrincipalName( version, entry.getPrincipalName() ); 132 133 // Compute the keyblock encoding 134 ByteBuffer keyBlockBuffer = encodeKeyBlock( entry.getKey() ); 135 136 int bufferSize = 137 4 + // size 138 principalNameBuffer.limit() + // principalName size 139 4 + // timeStamp 140 1 + // keyVersion 141 keyBlockBuffer.limit(); // keyBlock size 142 143 if ( version == Keytab.VERSION_0X502 ) 144 { 145 bufferSize += 4; // Add the principal NameType only for version 0x502 146 } 147 148 ByteBuffer buffer = ByteBuffer.allocate( bufferSize ); 149 150 // Store the size 151 buffer.putInt( bufferSize - 4 ); 152 153 // Store the principalNames 154 buffer.put( principalNameBuffer ); 155 156 // Store the principal type if version == 0x0502 157 if ( version == Keytab.VERSION_0X502 ) 158 { 159 buffer.putInt( ( int ) entry.getPrincipalType() ); 160 } 161 162 // Store the timeStamp 163 buffer.putInt( ( int ) ( entry.getTimeStamp().getTime() / 1000 ) ); 164 165 // Store the key version 166 buffer.put( entry.getKeyVersion() ); 167 168 // Store the KeyBlock 169 buffer.put( keyBlockBuffer ); 170 buffer.flip(); 171 172 return buffer; 173 } 174 175 176 /** 177 * Encode a principal name. 178 * 179 * @param buffer 180 * @param principalName 181 */ 182 private ByteBuffer encodePrincipalName( short version, String principalName ) 183 { 184 String[] split = principalName.split( "@" ); 185 String nameComponentPart = split[0]; 186 String realm = split[1]; 187 188 String[] nameComponents = nameComponentPart.split( "/" ); 189 190 // Compute the size of the buffer 191 List<byte[]> strings = new ArrayList<>(); 192 193 // Initialize the size with the number of components' size 194 int size = 2; 195 196 size += encodeCountedString( strings, realm ); 197 198 // compute NameComponents 199 for ( String nameComponent : nameComponents ) 200 { 201 size += encodeCountedString( strings, nameComponent ); 202 } 203 204 ByteBuffer buffer = ByteBuffer.allocate( size ); 205 206 // Now, write the data into the buffer 207 // store the numComponents 208 if ( version == Keytab.VERSION_0X501 ) 209 { 210 // increment for version 0x0501 211 buffer.putShort( ( short ) ( nameComponents.length + 1 ) ); 212 } 213 else 214 { 215 // Version = OxO502 216 buffer.putShort( ( short ) ( nameComponents.length ) ); 217 } 218 219 // Store the realm and the nameComponents 220 for ( byte[] string : strings ) 221 { 222 buffer.putShort( ( short ) ( string.length ) ); 223 buffer.put( string ); 224 } 225 226 buffer.flip(); 227 228 return buffer; 229 } 230 231 232 /** 233 * Encode a 16-bit encryption type and symmetric key material. 234 * 235 * We store the KeyType value ( a short ) and the KeyValue ( a length 236 * on a short and the bytes ) 237 */ 238 private ByteBuffer encodeKeyBlock( EncryptionKey key ) 239 { 240 byte[] keyBytes = key.getKeyValue(); 241 int size = 2 + 2 + keyBytes.length; // type, length, data 242 ByteBuffer buffer = ByteBuffer.allocate( size ); 243 244 // The type 245 buffer.putShort( ( short ) key.getKeyType().getValue() ); 246 247 // Use a prefixed 16-bit length to encode raw bytes. 248 buffer.putShort( ( short ) keyBytes.length ); 249 buffer.put( keyBytes ); 250 251 buffer.flip(); 252 253 return buffer; 254 } 255 256 257 /** 258 * Use a prefixed 16-bit length to encode a String. Realm and name 259 * components are ASCII encoded text with no zero terminator. 260 */ 261 private short encodeCountedString( List<byte[]> nameComponentBytes, String string ) 262 { 263 try 264 { 265 byte[] data = string.getBytes( "US-ASCII" ); 266 nameComponentBytes.add( data ); 267 268 return ( short ) ( data.length + 2 ); 269 } 270 catch ( UnsupportedEncodingException uee ) 271 { 272 throw new RuntimeException( uee.getMessage(), uee ); 273 } 274 } 275}