001/* 002 * Copyright 2025 Emmanuel Bourg 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package net.jsign.jca; 018 019import java.io.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.InputStream; 022import java.nio.ByteBuffer; 023import java.nio.ByteOrder; 024import java.nio.charset.StandardCharsets; 025import java.security.cert.CertificateException; 026import java.security.cert.CertificateFactory; 027import java.security.cert.X509Certificate; 028import java.util.ArrayList; 029import java.util.List; 030import javax.smartcardio.CardChannel; 031import javax.smartcardio.CardException; 032import javax.smartcardio.CommandAPDU; 033import javax.smartcardio.ResponseAPDU; 034 035/** 036 * Simple smart card interface for Certum cards (cryptoCertum 3.6 Common Profile). 037 * 038 * @since 7.4 039 */ 040public class CryptoCertumCard extends SmartCard { 041 042 /** AID of the eSign application with the Common profile */ 043 static final byte[] ESIGN_COMMON_PROFILE_AID = new byte[] { (byte) 0xA0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4F }; 044 045 /** AID of the eSign application with the Secure profile (eIDAS) */ 046 //static final byte[] ESIGN_SECURE_PROFILE_AID = new byte[] { (byte) 0xA0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4E }; 047 048 private CryptoCertumCard(CardChannel channel) throws CardException { 049 super(channel); 050 select(); 051 } 052 053 /** 054 * Select the eSign application on the card. 055 */ 056 private void select() throws CardException { 057 select("Certum", ESIGN_COMMON_PROFILE_AID); 058 } 059 060 /** 061 * Verify the PIN required for the protected operations. 062 * 063 * @param p1 0x00: verify, 0xFF: reset 064 * @param p2 0x83: PIN, 0x84: PUK 065 * @param pin the PIN 066 */ 067 public void verify(int p1, int p2, String pin) throws CardException { 068 if (pin == null) { 069 pin = ""; 070 } 071 byte[] mask = new byte[16]; // ASCII, zero-padded 072 System.arraycopy(pin.getBytes(), 0, mask, 0, pin.length()); 073 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0x20, p1, p2, pin.isEmpty() ? null : mask)); // VERIFY 074 handleError(response); 075 } 076 077 /** 078 * Get a challenge from the card. 079 * 080 * @param length the length of the challenge in bytes (8 or 16) 081 */ 082 public byte[] getChallenge(int length) throws CardException { 083 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0x84, 0x00, 0x00, length)); // GET CHALLENGE 084 handleError(response); 085 return response.getData(); 086 } 087 088 /** 089 * Get a file from the card. 090 * 091 * Known FIDs: 092 * <ul> 093 * <li>0x1021-0x102B: RSA Key #1-10</li> 094 * <li>0x1031-0x103B: EC Key #1-10</li> 095 * <li>0x2001-0x2015: Certificate #1-20</li> 096 * <li>0x5032: object directory</li> 097 * <li>0x5034: card info (factory id, serial number)</li> 098 * </ul> 099 * 100 * @param fid the identifier of the file 101 */ 102 public byte[] getFile(int fid) throws CardException { 103 return getFile(fid, false); 104 } 105 106 /** 107 * Get a file from the card. 108 * 109 * Known FIDs: 110 * <ul> 111 * <li>0x1021-0x102B: RSA Key #1-10</li> 112 * <li>0x1031-0x103B: EC Key #1-10</li> 113 * <li>0x2001-0x2015: Certificate #1-20</li> 114 * <li>0x5032: object directory</li> 115 * <li>0x5034: card info (factory id, serial number)</li> 116 * </ul> 117 * 118 * @param fid the identifier of the file 119 * @param partial if true, return only the first 256 bytes of the file 120 */ 121 public byte[] getFile(int fid, boolean partial) throws CardException { 122 int cacheId = partial ? (fid | 0x80000000) : fid; 123 if (dataObjectCache.containsKey(cacheId)) { 124 return dataObjectCache.get(cacheId); 125 } 126 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0xA4, 0x02, 0x0C, new byte[]{(byte) ((fid & 0xFF00) >> 8), (byte) (fid & 0xFF)})); // SELECT FILE 127 handleError(response); 128 129 byte[] data = readBinary(partial); 130 dataObjectCache.put(cacheId, data); 131 return data; 132 } 133 134 private byte[] readBinary(boolean partial) throws CardException { 135 int offset = 0; // page 136 int length = 256; // max length per page 137 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 138 while (true) { 139 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0xB0, offset & 0xFF, (offset & 0xFF00) >> 8, length)); // READ BINARY 140 handleError(response); 141 byte[] data = response.getData(); 142 bout.write(data, 0, data.length); 143 if (data.length < length || partial) { 144 break; 145 } 146 offset++; 147 } 148 149 return bout.toByteArray(); 150 } 151 152 /** 153 * Return the public key data (modulus for RSA keys) 154 * 155 * @param keyref the reference of the key 156 */ 157 public byte[] getKeyData(int keyref) throws CardException { 158 byte[] template = {(byte) 0xB6, 0x03, (byte) 0x83, 0x01, (byte) keyref, 0x7F, 0x49, 0x02, (byte) 0x81, 0x00}; 159 160 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0xCB, 0x00, 0xFF, template)); 161 handleError(response); 162 163 TLV tlv = TLV.parse(ByteBuffer.wrap(response.getData())); 164 165 return tlv.children().get(1).children().get(0).value(); 166 } 167 168 /** 169 * An entry on the card (key or certificate). 170 */ 171 public abstract class Entry { 172 /** Index of the object (1-10) */ 173 public int index; 174 175 public abstract int fid(); 176 177 public byte[] data() throws CardException { 178 return getFile(fid()); 179 } 180 181 public String name() throws CardException { 182 byte[] data = getFile(fid(), true); 183 int length = data[2]; 184 return new String(data, 3, length, StandardCharsets.UTF_8); 185 } 186 } 187 188 public class Key extends Entry { 189 /** Type of key (0: RSA key, 1: EC key) */ 190 public int type; 191 192 /** Key size in bits */ 193 public int size; 194 195 public byte ref() { 196 return (byte) ((type == 0 ? 0x20 : 0x30) + index); 197 } 198 199 public int fid() { 200 return 0x1000 + ref(); 201 } 202 } 203 204 public class Certificate extends Entry { 205 206 public int fid() { 207 return 0x2000 + index; 208 } 209 210 public X509Certificate getCertificate() throws CardException { 211 ByteBuffer buffer = ByteBuffer.wrap(data()).order(ByteOrder.BIG_ENDIAN); 212 buffer.position(0x84); 213 int length = buffer.getShort() & 0xFFFF; 214 215 byte[] data = new byte[length]; 216 buffer.get(data); 217 218 try { 219 InputStream in = new ByteArrayInputStream(data); 220 return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in); 221 } catch (CertificateException e) { 222 throw new CardException("Invalid data for certificate #" + index, e); 223 } 224 } 225 } 226 227 /** 228 * Return the list of keys and certificates available on the card. 229 */ 230 public List<Entry> getEntries() throws CardException { 231 byte[] data = getFile(0x5032); 232 233 List<Entry> objects = new ArrayList<>(); 234 for (int i = 0x24; i < data.length - 2; i += 2) { 235 int type = (data[i] & 0xF0) >> 4; 236 int index = data[i] & 0x0F; 237 238 Entry entry = null; 239 if (index != 0) { 240 if (type == 2) { 241 Certificate certificate = new Certificate(); 242 certificate.index = index; 243 entry = certificate; 244 245 } else { 246 Key key = new Key(); 247 key.index = index; 248 key.type = type; 249 250 // Key Algorithm (0x40: certificate, 0x41: RSA 2048 or P256, 0x42: RSA 3072 or P384, 0x43: RSA 4096 or P521, 0x44: RSA 1024) 251 int algorithm = data[i + 1]; 252 switch (type) { 253 case 0: // RSA 254 switch (algorithm) { 255 case 0x41: key.size = 2048; break; 256 case 0x42: key.size = 3072; break; 257 case 0x43: key.size = 4096; break; 258 case 0x44: key.size = 1024; break; 259 } 260 break; 261 case 1: // EC 262 switch (algorithm) { 263 case 0x41: key.size = 256; break; 264 case 0x42: key.size = 384; break; 265 case 0x43: key.size = 521; break; 266 } 267 break; 268 } 269 270 entry = key; 271 } 272 } 273 274 if (entry != null) { 275 objects.add(entry); 276 } 277 } 278 279 return objects; 280 } 281 282 /** 283 * Return the key with the specified name. 284 */ 285 public Key getKey(String name) throws CardException { 286 for (Entry entry : getEntries()) { 287 if (entry instanceof Key && entry.name().equals(name)) { 288 return (Key) entry; 289 } 290 } 291 return null; 292 } 293 294 /** 295 * Return the certificate with the specified name. 296 */ 297 public Certificate getCertificate(String name) throws CardException { 298 for (Entry entry : getEntries()) { 299 if (entry instanceof Certificate && entry.name().equals(name)) { 300 return (Certificate) entry; 301 } 302 } 303 return null; 304 } 305 306 /** 307 * Return the name of the keys available on the card. 308 */ 309 public List<String> aliases() throws CardException { 310 List<String> aliases = new ArrayList<>(); 311 for (Entry entry : getEntries()) { 312 if (entry instanceof Key) { 313 aliases.add(entry.name()); 314 } 315 } 316 return aliases; 317 } 318 319 /** 320 * Sign the specified hash with the specified key. 321 * 322 * @param key the key to use for signing 323 * @param hash the hash to sign 324 */ 325 public byte[] sign(Key key, byte[] hash) throws CardException { 326 if (pin != null) { 327 verify(0x00, 0x83, pin); 328 } 329 330 manageSecurityEnvironment(key); 331 hash(hash); 332 return computeDigitalSignature(); 333 } 334 335 /** 336 * Assign the specified key to the COMPUTE DIGITAL SIGNATURE operation 337 * 338 * @param key the key 339 */ 340 private void manageSecurityEnvironment(Key key) throws CardException { 341 byte[] template = new byte[] {(byte) 0x80, 0x01, (byte) (key.type == 0 ? 0x42 : 0x44), (byte) 0x84, 0x01, key.ref()}; 342 343 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0x22, 0x81, 0xB6, template)); // MANAGE SECURITY ENVIRONMENT 344 handleError(response); 345 } 346 347 /** 348 * Set the hash of the data to sign 349 */ 350 private void hash(byte[] hash) throws CardException { 351 TLV template = new TLV("90", hash); 352 353 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0x2A, 0x90, 0xA0, template.getEncoded())); // HASH 354 handleError(response); 355 } 356 357 /** 358 * Sign the specified data. 359 */ 360 private byte[] computeDigitalSignature() throws CardException { 361 ResponseAPDU response = transmit(new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, 0x80)); // COMPUTE DIGITAL SIGNATURE 362 if (response.getSW() == 0x6a88) { 363 throw new CardException("Signature key not found"); 364 } 365 handleError(response); 366 return response.getData(); 367 } 368 369 /** 370 * Get the CryptoCertum card. 371 */ 372 public static CryptoCertumCard getCard() throws CardException { 373 CardChannel channel = openChannel(ESIGN_COMMON_PROFILE_AID); 374 return channel != null ? new CryptoCertumCard(channel) : null; 375 } 376}