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.IOException; 020import java.math.BigInteger; 021import java.security.GeneralSecurityException; 022import java.security.KeyStoreException; 023import java.security.UnrecoverableKeyException; 024import java.security.cert.Certificate; 025import java.util.Arrays; 026import java.util.LinkedHashMap; 027import java.util.List; 028import javax.smartcardio.CardException; 029 030import org.bouncycastle.asn1.ASN1Encodable; 031import org.bouncycastle.asn1.ASN1Integer; 032import org.bouncycastle.asn1.DERSequence; 033 034import net.jsign.DigestAlgorithm; 035 036/** 037 * Signing service using a Certum smart card (cryptoCertum 3.6 with the Common Profile). 038 * 039 * @since 7.4 040 */ 041public class CryptoCertumCardSigningService implements SigningService { 042 043 private final CryptoCertumCard card; 044 045 public CryptoCertumCardSigningService(String pin) throws CardException { 046 CryptoCertumCard card = CryptoCertumCard.getCard(); 047 if (card == null) { 048 throw new CardException("CryptoCertum card not found"); 049 } 050 051 this.card = card; 052 this.card.verify(pin); 053 } 054 055 @Override 056 public String getName() { 057 return "CryptoCertum"; 058 } 059 060 @Override 061 public List<String> aliases() throws KeyStoreException { 062 try { 063 return card.aliases(); 064 } catch (CardException e) { 065 throw new KeyStoreException(e); 066 } 067 } 068 069 @Override 070 public Certificate[] getCertificateChain(String alias) throws KeyStoreException { 071 LinkedHashMap<String, Certificate> certificates = new LinkedHashMap<>(); 072 073 try { 074 CryptoCertumCard.Certificate certificate = card.getCertificate(alias); 075 if (certificate != null) { 076 certificates.put(alias, certificate.getCertificate()); 077 } 078 } catch (CardException e) { 079 throw new KeyStoreException(e); 080 } 081 082 return certificates.values().toArray(new Certificate[0]); 083 } 084 085 @Override 086 public SigningServicePrivateKey getPrivateKey(String alias, char[] password) throws UnrecoverableKeyException { 087 try { 088 CryptoCertumCard.Key key = card.getKey(alias); 089 if (key != null) { 090 SigningServicePrivateKey privateKey = new SigningServicePrivateKey(alias, key.type == 0 ? "RSA" : "ECDSA", this); 091 privateKey.getProperties().put("key", key); 092 return privateKey; 093 } 094 } catch (CardException e) { 095 throw (UnrecoverableKeyException) new UnrecoverableKeyException().initCause(e); 096 } 097 098 throw new UnrecoverableKeyException("Key '" + alias + "' not found on the card"); 099 } 100 101 @Override 102 public byte[] sign(SigningServicePrivateKey privateKey, String algorithm, byte[] data) throws GeneralSecurityException { 103 DigestAlgorithm digestAlgorithm = DigestAlgorithm.of(algorithm.substring(0, algorithm.toLowerCase().indexOf("with"))); 104 byte[] digest = digestAlgorithm.getMessageDigest().digest(data); 105 106 CryptoCertumCard.Key key = (CryptoCertumCard.Key) privateKey.getProperties().get("key"); 107 108 try { 109 if ("RSA".equals(privateKey.getAlgorithm())) { 110 // RSA 111 return card.sign(key, digest); 112 } else { 113 // ECDSA 114 byte[] content; 115 if (digest.length > key.size / 8) { 116 content = Arrays.copyOf(digest, key.size / 8); 117 } else { 118 content = digest; 119 } 120 121 return toEcdsaSigValue(card.sign(key, content)); 122 } 123 } catch (CardException | IOException e) { 124 throw new GeneralSecurityException(e); 125 } 126 } 127 128 /** 129 * ECDSA signatures are returned as two integers, r and s, concatenated together (IEEE P1363 format). 130 * This method wraps the two integers into an Ecdsa-Sig-Value ASN.1 structure (RFC 3279, sec 2.2.3). 131 */ 132 private byte[] toEcdsaSigValue(byte[] p1363signature) throws IOException { 133 DERSequence ecdsaSigValue = new DERSequence(new ASN1Encodable[]{ 134 new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(p1363signature, 0, p1363signature.length / 2))), // r 135 new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(p1363signature, p1363signature.length / 2, p1363signature.length))) // s 136 }); 137 138 return ecdsaSigValue.getEncoded("DER"); 139 } 140}