Son dos clases:
- AppTest. Clase con el main que desencadena el ejemplo
- UtilsDnie. Clase con los métodos para detectar el lector, pedir el dni, firmar y verificar la firma
- Librería /usr/lib/i386-linux-gnu/libpcsclite.so de manejo de lectores de tarjetas.
- Librería /usr/lib/opensc-pkcs11.so de manejo de las funcionalidades criptográficas del DNIe.
package ord.edu.app;
public class AppTest {
private static final String TEXTO_A_FIRMAR = "texto de ejemplo a firmar";
private static final String PIN_ACCESO_DNIE = "xxxxxxxxxx";
public static void main(String[] args) throws Exception {
final UtilsDNIe ud = new UtilsDNIe();
//Solicitamos el DNIe
if(ud.solicitaDNI()) {
//Si se ha insertado el DNIe firmamos
final byte[] firma = ud.firmaDatos(PIN_ACCESO_DNIE,
TEXTO_A_FIRMAR.getBytes());
//Verificamos la firma
final boolean resultadoFirma = ud.verificaFirmaDatos(PIN_ACCESO_DNIE,
TEXTO_A_FIRMAR.getBytes(),
firma);
System.out.println(resultadoFirma);
}
}
}
Clase UtilsDnie
package ord.edu.app;
import java.io.ByteArrayInputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.List;
import javax.smartcardio.Card;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.TerminalFactory;
import sun.security.pkcs11.SunPKCS11;
public class UtilsDNIe {
private static final String CERT_AUTEN = "CertAutenticacion";
private static final String CERT_FIRMA = "CertFirmaDigital";
private static final long TIMEOUT_INSERCION_DNIE = 5000;
/**
* Solicita al usuario la inclusión del DNIe en cualquiera de los lectores
* de tarjetas PC/SC detectados.
*
* PC/SC (Personal Computer/Smart Card) es un conjunto de especificaciones para la
* integración de tarjetas inteligentes en ordenadores personales. En particular se
* define un API de programación que permite a los desarrolladores trabajar de forma
* uniforme con lectores de tarjetas de distintos fabricantes (que cumplan con la
* especificación).
*
* Como tenemos instalada la libería libpcsclite.so, accedemos al DNIe mediante el
* proveedor SunPCSC, ajustando la propiedad "sun.security.smartcardio.library" del
* sistema tal y como se documenta en la URL:
* http://docs.oracle.com/javase/6/docs/technotes/guides/security/
* SunProviders.html#SunPCSCProvider
*
* @return Card conectado al DNIe
* @throws CardException Se se ha detectado algú problema con la tarjeta
*/
public boolean solicitaDNI() throws CardException {
System.setProperty("sun.security.smartcardio.library",
"/usr/lib/i386-linux-gnu/libpcsclite.so");
//Recorremos la lista de lectores detectado pidiendo el DNIe
Card card = null;
CardTerminal terminal;
final TerminalFactory factory = TerminalFactory.getDefault();
final List<CardTerminal> terminals = factory.terminals().list();
for (int i = 0; i < terminals.size(); i++) {
terminal = terminals.get(i);
//Por darle un punto interactivo indicamos al usuario
//que inserte el DNIe y esperamos un poco
System.out.println("Inserte su DNIe en: "+terminal.getName());
terminal.waitForCardPresent(TIMEOUT_INSERCION_DNIE);
if(terminal.isCardPresent()) {
System.out.println("Detectada tarjeta, conectando");
//Conectamos usando cualquier protocolo "*"
card = terminal.connect("*");
break;
} else {
System.out.println("No se ha detectado el DNIe en: "+terminal.getName());
}
}
//Se ha detectado el DNIe, devolvemos true y desconectamos reseteando la conexión
if (card!=null){
card.disconnect(true);
return true;
}
//No se ha detectado el DNIe, devolvemos false
return false;
}
/**
* Firmamos los datos recibidos en el DNIe
*
* @param pinAcceso Número PIN de acceso al DNIe
* @param datos Datos a firmar
* @return Datos firmados
* @throws Exception Problemas durante el proceso de firmado
*/
public byte[] firmaDatos(String pinAcceso, byte[] datos) throws Exception {
//Ajustamos el proveedor PKCS11
final SunPKCS11 sunpkcs11 = ajustaPKCS11ProviderUbuntu();
//Accedemos al almacen de certificados del DNIe con el pin.
final KeyStore keyStore = KeyStore.getInstance("PKCS11", sunpkcs11);
char[] pin = pinAcceso.toCharArray();
keyStore.load(null, pin);
//Recogemos la lista de alias de certificados del dispositivo
final Enumeration<String> enumeration = keyStore.aliases();
//Buscamos el alias del certificado de Firma, en el DNIe hay dos, uno para
//firmar (CertFirmaDigital) y otro para autenticarse (CertAutenticacion)
String alias = null;
while(enumeration.hasMoreElements()) {
alias = enumeration.nextElement().toString();
if(CERT_FIRMA.equals(alias)) {
break;
}
}
//Se se ha encontrado el certificado de firma seguimos
if(CERT_FIRMA.equals(alias)) {
//Recogemos la clave privada del certificado de firma
final Key key = keyStore.getKey(CERT_FIRMA, pin);
//Firmamos (en el dnie) los datos recibidos
final Signature firmador = Signature.getInstance("SHA1withRSA");
firmador.initSign((PrivateKey)key);
firmador.update(datos);
final byte[] firma = firmador.sign();
//devolvemos la firma
return firma;
} else {
throw new Exception("No se encontro el certificado CertFirmaDigital de firma");
}
}
/**
* Validamos la firma recibida en el DNIe
*
* @param pinAcceso Número PIN de acceso al DNIe
* @param datos Datos que se han firmado
* @param firma Firma a verificar
* @return Resultado de la validación de la firma
* @throws Exception Problemas durante el proceso de validación
*/
public boolean verificaFirmaDatos(String pinAcceso, byte[] datos, byte[] firma)
throws Exception {
//Ajustamos el proveedor PKCS11
final SunPKCS11 sunpkcs11 = ajustaPKCS11ProviderUbuntu();
//Accedemos al almacen de certificados del DNIe con el pin.
final KeyStore keyStore = KeyStore.getInstance("PKCS11", sunpkcs11);
char[] pin = pinAcceso.toCharArray();
keyStore.load(null, pin);
//Recogemos la lista de alias de certificados del dispositivo
final Enumeration<String> enumeration = keyStore.aliases();
//Buscamos el alias del certificado de Firma, en el DNIe hay dos, uno para
//firmar (CertFirmaDigital) y otro para autenticarse (CertAutenticacion)
String alias = null;
while(enumeration.hasMoreElements()) {
alias = enumeration.nextElement().toString();
if(CERT_FIRMA.equals(alias)) {
break;
}
}
//Se se ha encontrado el certificado de firma seguimos
if(CERT_FIRMA.equals(alias)) {
//Recogemos el certificado de firma
final Certificate certificado = keyStore.getCertificate(alias);
//Validamos la firma de los datos (en el dnie)
final Signature verificadorFirma = Signature.getInstance("SHA1withRSA");
verificadorFirma.initVerify(certificado.getPublicKey());
verificadorFirma.update(datos);
return verificadorFirma.verify(firma);
} else {
throw new Exception("No se encontro el certificado CertFirmaDigital de firma");
}
}
/**
* Ajustamos el proveedor SunPKCS11 apuntando a las librerías nativas pkcs11
* del sistema. El proveedor SunPKCS11 no realiza funciones criptográficas
* por si mismo, sino que permite a las aplicaciones Java usar los APIS JCA/JCE
* para acceder a estas funcionalidades en las librerías nativas pkcs11.
*/
private SunPKCS11 ajustaPKCS11ProviderUbuntu() {
final String pkcs11config = "name = DNIe\nlibrary = /usr/lib/opensc-pkcs11.so\n";
final SunPKCS11 sunpkcs11 = new SunPKCS11(
new ByteArrayInputStream(pkcs11config.getBytes()));
Security.addProvider(sunpkcs11);
return sunpkcs11;
//TODO: Amplicar a otros sistemas operativos
}
}
Un buen recordatorio para hacerlo.
ResponderEliminarAmigo, una pregunta. Sabes como matar la conexión con la lectora. Sucede que tengo un proyecto y el System.exit(0) no funciona y es porque como que la conexión entre lectora y java queda aún.
ResponderEliminarHola
ResponderEliminarTuve problemas parecidos pero no era la conexión Java-Lectora sino el navegador que dejaba abierta una sesión con el lector de tarjetas cuando accedía a alguna página https. ¿Es una aplicación de escritorio o un applet?
Un saludo
No me funciona, intento copiar el codigo en una raspberry pi y me sale este errir>
ResponderEliminarAppTest.java:10: error: cannot find symbol
final UtilsDNIe ud = new UtilsDNIe();
^
symbol: class UtilsDNIe
location: class AppTest
AppTest.java:10: error: cannot find symbol
final UtilsDNIe ud = new UtilsDNIe();
^
symbol: class UtilsDNIe
location: class AppTest
2 errors
Alguna ayuda
Hola
ResponderEliminarMe da la sensación de que el pseudojava que maneja tu 'raspberry pi' al igual que el pseudojava que maneja Arduino, no entiende la especificación 'final' que meto en el ejemplo para indicar a Java como manejar esa variable en memoria. Elimina los final del código y prueba.