001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.security; 020 021import java.io.IOException; 022import java.io.StringReader; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.LinkedHashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Objects; 031import java.util.function.ToIntFunction; 032import org.jpos.iso.ISOUtil; 033 034/** 035 * The builder class to create and parse key block structure. 036 */ 037public class SecureKeyBlockBuilder { 038 039 protected static final int SIZE_KEYBLOCK_VERSION = 1; 040 041 protected static final int SIZE_KEYBLOCK_LENGTH = 4; 042 043 protected static final int SIZE_KEYUSAGE = 2; 044 045 protected static final int SIZE_KEY_VERSION = 2; 046 047 protected static final int SIZE_NUMOFOPTHDR = 2; 048 049 protected static final int SIZE_RESERVED = 2; 050 051 protected static final int SIZE_HEADER = 16; 052 053 protected static final int SIZE_OPTHDR_ID = 2; 054 055 protected static final int SIZE_OPTHDR_LENGTH = 2; 056 057 protected static final int SIZE_HEADER_3DES = 8; 058 059 protected static final int SIZE_HEADER_AES = 16; 060 061 private final List<Character> versionsWith8CharacterMAC = new ArrayList<>( 062 Arrays.asList( 063 'A' // TR-31:2005 'A' Key block protected using the Key Variant Binding Method 064 ,'B' // TR-31:2010 'B' Key block protected using the Key Derivation Binding Method 065 ,'C' // TR-31:2010 'C' Key block protected using the Key Variant Binding Method 066 ,'0' // Proprietary '0' Key block protected using the 3-DES key 067 ) 068 ); 069 070 /** 071 * Don't let anyone instantiate this class. 072 */ 073 private SecureKeyBlockBuilder() { 074 } 075 076 public static SecureKeyBlockBuilder newBuilder() { 077 return new SecureKeyBlockBuilder(); 078 } 079 080 /** 081 * Configure key block versions with 8 digits key block MAC. 082 * <p> 083 * Default 8 digits <i>(4 bytes)</i> key block MAC versions are: 084 * <ul> 085 * <li>'A' TR-31:2005 Key block protected using the Key Variant Binding Method 086 * <li>'B' TR-31:2010 Key block protected using the Key Derivation Binding Method 087 * <li>'C' TR-31:2010 Key block protected using the Key Variant Binding Method 088 * <li>'0' Proprietary Key block protected using the 3-DES key 089 * </ul> 090 * @param versions the string with versions characters 091 * @return This builder instance 092 */ 093 public SecureKeyBlockBuilder with8characterMACVersions(String versions) { 094 Objects.requireNonNull(versions, "The versions with 8 digits MAC cannot be null"); 095 versionsWith8CharacterMAC.clear(); 096 for (Character ch : versions.toCharArray()) 097 versionsWith8CharacterMAC.add(ch); 098 099 return this; 100 } 101 102 protected int getMACLength(SecureKeyBlock skb) { 103 if (versionsWith8CharacterMAC.contains(skb.getKeyBlockVersion())) 104 return SIZE_HEADER_3DES; 105 106 return SIZE_HEADER_AES; 107 } 108 109 110 protected static String readString(StringReader sr, int len) { 111 char[] chars = new char[len]; 112 try { 113 sr.read(chars); 114 } catch (IOException ex) { 115 throw new IllegalArgumentException("Problem witch reading key block characters", ex); 116 } 117 return String.valueOf(chars); 118 } 119 120 protected static char readChar(StringReader sr) { 121 try { 122 return (char) sr.read(); 123 } catch (IOException ex) { 124 throw new IllegalArgumentException("Problem witch reading key block character", ex); 125 } 126 } 127 128 protected static Map<String, String> parseOptionalHeader(StringReader sr, int numOfBlocks) { 129 Map<String, String> ret = new LinkedHashMap<>(); 130 int cnt = numOfBlocks; 131 String hbi; 132 int len; 133 String hdata; 134 while (cnt-- > 0) { 135 hbi = readString(sr, SIZE_OPTHDR_ID); 136 len = Integer.valueOf(readString(sr, SIZE_OPTHDR_LENGTH), 0x10); 137 hdata = readString(sr, len - SIZE_OPTHDR_ID - SIZE_OPTHDR_LENGTH); 138 ret.put(hbi, hdata); 139 } 140 return ret; 141 } 142 143 protected static int calcOptionalHeaderLength(Map<String, String> optHdrs) { 144 ToIntFunction<String> entryLength = e -> { 145 int l = SIZE_OPTHDR_ID + SIZE_OPTHDR_LENGTH; 146 if (e != null) 147 l += e.length(); 148 149 return l; 150 }; 151 Collection<String> c = optHdrs.values(); 152 return c.stream().mapToInt(entryLength).sum(); 153 } 154 155 public SecureKeyBlock build(CharSequence data) throws IllegalArgumentException { 156 Objects.requireNonNull(data, "The key block data cannot be null"); 157 SecureKeyBlock skb = new SecureKeyBlock(); 158 String keyblock = data.toString(); 159 if (keyblock.length() < SIZE_HEADER) 160 throw new IllegalArgumentException("The key block data cannot be shorter than 16"); 161 162 StringReader sr = new StringReader(data.toString()); 163 skb.keyBlockVersion = readChar(sr); 164 skb.keyBlockLength = Integer.valueOf(readString(sr, SIZE_KEYBLOCK_LENGTH)); 165 String ku = readString(sr, SIZE_KEYUSAGE); 166 skb.keyUsage = ExtKeyUsage.valueOfByCode(ku); 167 skb.algorithm = Algorithm.valueOfByCode(readChar(sr)); 168 skb.modeOfUse = ModeOfUse.valueOfByCode(readChar(sr)); 169 skb.keyVersion = readString(sr, SIZE_KEY_VERSION); 170 skb.exportability = Exportability.valueOfByCode(readChar(sr)); 171 int numOfBlocks = Integer.valueOf(readString(sr, SIZE_NUMOFOPTHDR)); 172 skb.reserved = readString(sr, SIZE_RESERVED); 173 skb.optionalHeaders = parseOptionalHeader(sr, numOfBlocks); 174 int consumed = SIZE_HEADER + calcOptionalHeaderLength(skb.getOptionalHeaders()); 175 176 if (skb.getKeyBlockLength() <= consumed) 177 // it can be but it should not occur 178 return skb; 179 180 int remain = skb.getKeyBlockLength() - consumed; 181 int macLen = getMACLength(skb); 182 String keyEnc = readString(sr, remain - macLen); 183 if (!keyEnc.isEmpty()) 184 skb.setKeyBytes(ISOUtil.hex2byte(keyEnc)); 185 186 String mac = readString(sr, macLen); 187 skb.keyBlockMAC = ISOUtil.hex2byte(mac); 188 return skb; 189 } 190 191 public String toKeyBlock(SecureKeyBlock skb) { 192 StringBuilder sb = new StringBuilder(); 193 sb.append(skb.getKeyBlockVersion()); 194 sb.append(String.format("%04d", skb.getKeyBlockLength())); 195 sb.append(skb.getKeyUsage().getCode()); 196 sb.append(skb.getAlgorithm().getCode()); 197 sb.append(skb.getModeOfUse().getCode()); 198 sb.append(skb.getKeyVersion()); 199 sb.append(skb.getExportability().getCode()); 200 201 Map<String, String> optHdr = skb.getOptionalHeaders(); 202 sb.append(String.format("%02d", optHdr.size())); 203 sb.append(skb.getReserved()); 204 205 for (Entry<String, String> ent : optHdr.entrySet()) { 206 sb.append(ent.getKey()); 207 sb.append(String.format("%02X", ent.getValue().length() + SIZE_OPTHDR_ID + SIZE_OPTHDR_LENGTH)); 208 sb.append(ent.getValue()); 209 } 210 211 byte[] b = skb.getKeyBytes(); 212 if (b != null) 213 sb.append(ISOUtil.hexString(b)); 214 215 b = skb.getKeyBlockMAC(); 216 if (b != null) 217 sb.append(ISOUtil.hexString(skb.getKeyBlockMAC())); 218 219 return sb.toString(); 220 } 221 222}