/*
* Controlador Java de la Secretaria de Estado de Administraciones Publicas
* para el DNI electronico.
*
* El Controlador Java para el DNI electronico es un proveedor de seguridad de JCA/JCE
* que permite el acceso y uso del DNI electronico en aplicaciones Java de terceros
* para la realizacion de procesos de autenticacion, firma electronica y validacion
* de firma. Para ello, se implementan las funcionalidades KeyStore y Signature para
* el acceso a los certificados y claves del DNI electronico, asi como la realizacion
* de operaciones criptograficas de firma con el DNI electronico. El Controlador ha
* sido disenado para su funcionamiento independiente del sistema operativo final.
*
* Copyright (C) 2012 Direccion General de Modernizacion Administrativa, Procedimientos
* e Impulso de la Administracion Electronica
*
* Este programa es software libre y utiliza un licenciamiento dual (LGPL 2.1+
* o EUPL 1.1+), lo cual significa que los usuarios podran elegir bajo cual de las
* licencias desean utilizar el codigo fuente. Su eleccion debera reflejarse
* en las aplicaciones que integren o distribuyan el Controlador, ya que determinara
* su compatibilidad con otros componentes.
*
* El Controlador puede ser redistribuido y/o modificado bajo los terminos de la
* Lesser GNU General Public License publicada por la Free Software Foundation,
* tanto en la version 2.1 de la Licencia, o en una version posterior.
*
* El Controlador puede ser redistribuido y/o modificado bajo los terminos de la
* European Union Public License publicada por la Comision Europea,
* tanto en la version 1.1 de la Licencia, o en una version posterior.
*
* Deberia recibir una copia de la GNU Lesser General Public License, si aplica, junto
* con este programa. Si no, consultelo en <http://www.gnu.org/licenses/>.
*
* Deberia recibir una copia de la European Union Public License, si aplica, junto
* con este programa. Si no, consultelo en <http://joinup.ec.europa.eu/software/page/eupl>.
*
* Este programa es distribuido con la esperanza de que sea util, pero
* SIN NINGUNA GARANTIA; incluso sin la garantia implicita de comercializacion
* o idoneidad para un proposito particular.
*/
package es.gob.jmulticard.jse.provider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.macs.CMac;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.ECPointUtil;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.jce.spec.ECNamedCurveSpec;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import es.gob.jmulticard.CryptoHelper;
/** Funcionalidades criptográficas de utilidad implementadas mediante proveedores de seguridad JSE6.
* @author Tomás García-Merás */
public final class JseCryptoHelper extends CryptoHelper {
private static final Logger LOGGER = Logger.getLogger("es.gob.jmulticard"); //$NON-NLS-1$
private static final String ECDH = "ECDH"; //$NON-NLS-1$
/** {@inheritDoc} */
@Override
public byte[] digest(final DigestAlgorithm algorithm, final byte[] data) throws IOException {
if (algorithm == null) {
throw new IllegalArgumentException("El algoritmo de huella digital no puede ser nulo"); //$NON-NLS-1$
}
if (data == null) {
throw new IllegalArgumentException("Los datos para realizar la huella digital no pueden ser nulos"); //$NON-NLS-1$
}
try {
return MessageDigest.getInstance(algorithm.toString()).digest(data);
}
catch (final NoSuchAlgorithmException e) {
throw new IOException(
"El sistema no soporta el algoritmo de huella digital indicado ('" + algorithm + "'): " + e, e //$NON-NLS-1$ //$NON-NLS-2$
);
}
}
private static byte[] doDesede(final byte[] data, final byte[] key, final int direction) throws IOException {
final byte[] ivBytes = new byte[8];
for (int i = 0; i < 8; i++) {
ivBytes[i] = 0x00;
}
final SecretKey k = new SecretKeySpec(prepareDesedeKey(key), "DESede"); //$NON-NLS-1$
try {
final Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding"); //$NON-NLS-1$
cipher.init(direction, k, new IvParameterSpec(ivBytes));
final byte[] cipheredData = cipher.doFinal(data);
// Machacamos los datos para evitar que queden en memoria
for(int i=0;i<data.length;i++) {
data[i] = '\0';
}
return cipheredData;
}
catch (final Exception e) {
// Machacamos los datos para evitar que queden en memoria
for(int i=0;i<data.length;i++) {
data[i] = '\0';
}
throw new IOException("Error encriptando datos: " + e, e); //$NON-NLS-1$
}
}
/** {@inheritDoc} */
@Override
public byte[] desedeEncrypt(final byte[] data, final byte[] key) throws IOException {
return doDesede(data, key, Cipher.ENCRYPT_MODE);
}
/** {@inheritDoc} */
@Override
public byte[] desedeDecrypt(final byte[] data, final byte[] key) throws IOException {
return doDesede(data, key, Cipher.DECRYPT_MODE);
}
private static byte[] prepareDesedeKey(final byte[] key) {
if (key == null) {
throw new IllegalArgumentException("La clave 3DES no puede ser nula"); //$NON-NLS-1$
}
if (key.length == 24) {
return key;
}
if (key.length == 16) {
final byte[] newKey = new byte[24];
System.arraycopy(key, 0, newKey, 0, 16);
System.arraycopy(key, 0, newKey, 16, 8);
return newKey;
}
throw new IllegalArgumentException("Longitud de clave invalida, se esperaba 16 o 24, pero se indico " + Integer.toString(key.length)); //$NON-NLS-1$
}
private static byte[] doDes(final byte[] data, final byte[] key, final int direction) throws IOException {
if (key == null) {
throw new IllegalArgumentException("La clave DES no puede ser nula"); //$NON-NLS-1$
}
if (key.length != 8) {
throw new IllegalArgumentException(
"La clave DES debe ser de 8 octetos, pero la proporcionada es de " + key.length //$NON-NLS-1$
);
}
try {
final Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); //$NON-NLS-1$
cipher.init(direction, new SecretKeySpec(key, "DES")); //$NON-NLS-1$
return cipher.doFinal(data);
}
catch (final Exception e) {
throw new IOException("Error cifrando los datos con DES: " + e, e); //$NON-NLS-1$
}
}
/** {@inheritDoc} */
@Override
public byte[] desEncrypt(final byte[] data, final byte[] key) throws IOException {
return doDes(data, key, Cipher.ENCRYPT_MODE);
}
/** {@inheritDoc} */
@Override
public byte[] desDecrypt(final byte[] data, final byte[] key) throws IOException {
return doDes(data, key, Cipher.DECRYPT_MODE);
}
private static byte[] doRsa(final byte[] cipheredData, final Key key, final int direction) throws IOException {
try {
final Cipher dec = Cipher.getInstance("RSA/ECB/NOPADDING"); //$NON-NLS-1$
dec.init(direction, key);
return dec.doFinal(cipheredData);
}
catch (final Exception e) {
throw new IOException("Error cifrando/descifrando los datos mediante la clave RSA: " + e, e); //$NON-NLS-1$
}
}
/** {@inheritDoc} */
@Override
public byte[] rsaDecrypt(final byte[] cipheredData, final Key key) throws IOException {
return doRsa(cipheredData, key, Cipher.DECRYPT_MODE);
}
/** {@inheritDoc} */
@Override
public byte[] rsaEncrypt(final byte[] data, final Key key) throws IOException {
return doRsa(data, key, Cipher.ENCRYPT_MODE);
}
/** {@inheritDoc} */
@Override
public Certificate generateCertificate(final byte[] encode) throws CertificateException {
return CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(encode)); //$NON-NLS-1$
}
/** {@inheritDoc} */
@Override
public byte[] generateRandomBytes(final int numBytes) throws IOException {
final SecureRandom sr;
try {
sr = SecureRandom.getInstance("SHA1PRNG"); //$NON-NLS-1$
}
catch (final NoSuchAlgorithmException e) {
throw new IOException("Algoritmo de generacion de aleatorios no valido: " + e, e); //$NON-NLS-1$
}
final byte[] randomBytes = new byte[numBytes];
sr.nextBytes(randomBytes);
return randomBytes;
}
private static byte[] aesCrypt(final byte[] data, final byte[] iv, final byte[] key, final int mode) throws IOException {
if (data == null) {
throw new IllegalArgumentException(
"Los datos a cifrar no pueden ser nulos" //$NON-NLS-1$
);
}
if (key == null) {
throw new IllegalArgumentException(
"La clave de cifrado no puede ser nula" //$NON-NLS-1$
);
}
final Cipher aesCipher;
try {
aesCipher = Cipher.getInstance("AES/CBC/NoPadding"); //$NON-NLS-1$
}
catch (final Exception e) {
throw new IOException(
"No se ha podido obtener una instancia del cifrador 'AES/CBC/NoPadding': " + e, e //$NON-NLS-1$
);
}
// Vector de inicializacion
final byte[] ivector;
if (iv == null) {
// Creamos el IV de forma aleatoria, porque ciertos proveedores (como Android) dan arrays fijos
// para IvParameterSpec.getIV(), normalmente todo ceros
LOGGER.info("Se usara un vector de inicializacion AES aleatorio"); //$NON-NLS-1$
ivector = new byte[aesCipher.getBlockSize()];
new SecureRandom().nextBytes(ivector);
}
else if (iv.length == 0) {
LOGGER.warning("Se usara un vector de inicializacion AES vacio"); //$NON-NLS-1$
ivector = new byte[aesCipher.getBlockSize()];
}
else {
ivector = iv;
}
try {
aesCipher.init(
mode,
new SecretKeySpec(key, "AES"), //$NON-NLS-1$
new IvParameterSpec(ivector)
);
}
catch (final Exception e) {
throw new IOException(
"La clave proporcionada no es valida: " + e, e//$NON-NLS-1$
);
}
try {
return aesCipher.doFinal(data);
}
catch (final Exception e) {
throw new IOException(
"Error en el descifrado, posiblemente los datos proporcionados no sean validos: " + e, e//$NON-NLS-1$
);
}
}
@Override
public byte[] aesDecrypt(final byte[] data, final byte[] iv, final byte[] key) throws IOException {
return aesCrypt(data, iv, key, Cipher.DECRYPT_MODE);
}
@Override
public byte[] aesEncrypt(final byte[] data, final byte[] iv, final byte[] key) throws IOException {
return aesCrypt(data, iv, key, Cipher.ENCRYPT_MODE);
}
@Override
public KeyPair generateEcKeyPair(final EcCurve curveName) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance(ECDH, BouncyCastleProvider.PROVIDER_NAME);
}
catch (final Exception e) {
Logger.getLogger("es.gob.jmulticard").warning( //$NON-NLS-1$
"No se ha podido obtener un generador de pares de claves de curva eliptica con BouncyCastle, se usara el generador por defecto: " + e //$NON-NLS-1$
);
kpg = KeyPairGenerator.getInstance(ECDH);
}
Logger.getLogger("es.gob.jmulticard").info( //$NON-NLS-1$
"Seleccionado el siguiente generador de claves de curva eliptica: " + kpg.getClass().getName() //$NON-NLS-1$
);
final AlgorithmParameterSpec parameterSpec = new ECGenParameterSpec(curveName.toString());
kpg.initialize(parameterSpec);
return kpg.generateKeyPair();
}
@Override
public byte[] doAesCmac(final byte[] data, final byte[] key) {
final BlockCipher cipher = new AESEngine();
final org.spongycastle.crypto.Mac mac = new CMac(cipher, 64);
final KeyParameter keyP = new KeyParameter(key);
mac.init(keyP);
mac.update(data, 0, data.length);
final byte[] out = new byte[8];
mac.doFinal(out, 0);
return out;
}
@Override
public final byte[] doEcDh(final Key privateKey,
final byte[] publicKey,
final EcCurve curveName) throws NoSuchAlgorithmException,
InvalidKeyException,
InvalidKeySpecException {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
KeyAgreement ka = null;
try {
ka = KeyAgreement.getInstance(ECDH, BouncyCastleProvider.PROVIDER_NAME);
}
catch (final NoSuchProviderException e) {
LOGGER.warning(
"No se ha podido obtener el KeyAgreement ECDH de BouncyCastle, se intentara el por defecto: " + e //$NON-NLS-1$
);
ka = KeyAgreement.getInstance(ECDH);
}
ka.init(privateKey);
ka.doPhase(loadEcPublicKey(publicKey, curveName), true);
return ka.generateSecret();
}
private static Key loadEcPublicKey(final byte [] pubKey,
final EcCurve curveName) throws NoSuchAlgorithmException,
InvalidKeySpecException {
final ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName.toString());
KeyFactory kf;
try {
kf = KeyFactory.getInstance(ECDH, BouncyCastleProvider.PROVIDER_NAME);
}
catch (final NoSuchProviderException e) {
LOGGER.warning(
"No se ha podido obtener el KeyAgreement ECDH de BouncyCastle, se intentara el por defecto: " + e //$NON-NLS-1$
);
kf = KeyFactory.getInstance(ECDH);
}
final ECNamedCurveSpec params = new ECNamedCurveSpec(curveName.toString(), spec.getCurve(), spec.getG(), spec.getN());
final ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
final java.security.spec.ECPublicKeySpec pubKeySpec = new java.security.spec.ECPublicKeySpec(point, params);
return kf.generatePublic(pubKeySpec);
}
@Override
public AlgorithmParameterSpec getEcPoint(final byte[] nonceS, final byte[] sharedSecretH, final EcCurve curveName) {
final AlgorithmParameterSpec ecParams = ECNamedCurveTable.getParameterSpec(curveName.toString());
final BigInteger affineX = os2i(sharedSecretH);
final BigInteger affineY = computeAffineY(affineX, (ECParameterSpec) ecParams);
final ECPoint sharedSecretPointH = new ECPoint(affineX, affineY);
return mapNonceGMWithECDH(os2i(nonceS), sharedSecretPointH, (ECParameterSpec) ecParams);
}
/** Convierte un Octet String de ASN.1 en un entero
* (según <i>BSI TR 03111</i> Sección 3.1.2).
* @param bytes Octet String de ASN.1.
* @return Entero (siempre positivo). */
private static BigInteger os2i(final byte[] bytes) {
if (bytes == null) { throw new IllegalArgumentException(); }
return os2i(bytes, 0, bytes.length);
}
/** Convierte un Octet String de ASN.1 en un entero
* (según <i>BSI TR 03111</i> Sección 3.1.2).
* @param Octet String de ASN.1.
* @param offset posición de inicio-
* @param length longitud del Octet String.
* @return Entero (siempre positivo). */
private static BigInteger os2i(final byte[] bytes, final int offset, final int length) {
if (bytes == null) {
throw new IllegalArgumentException("El Octet String no puede ser nulo"); //$NON-NLS-1$
}
BigInteger result = BigInteger.ZERO;
final BigInteger base = BigInteger.valueOf(256);
for (int i = offset; i < offset + length; i++) {
result = result.multiply(base);
result = result.add(BigInteger.valueOf(bytes[i] & 0xFF));
}
return result;
}
private static BigInteger computeAffineY(final BigInteger affineX, final ECParameterSpec params) {
final ECCurve bcCurve = toBouncyCastleECCurve(params);
final ECFieldElement a = bcCurve.getA();
final ECFieldElement b = bcCurve.getB();
final ECFieldElement x = bcCurve.fromBigInteger(affineX);
final ECFieldElement y = x.multiply(x).add(a).multiply(x).add(b).sqrt();
return y.toBigInteger();
}
private static ECCurve toBouncyCastleECCurve(final ECParameterSpec params) {
final EllipticCurve curve = params.getCurve();
final ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) {
throw new IllegalArgumentException(
"Solo se soporta 'ECFieldFp' y se proporciono " + field.getClass().getCanonicalName() //$NON-NLS-1$
);
}
final int coFactor = params.getCofactor();
final BigInteger order = params.getOrder();
final BigInteger a = curve.getA();
final BigInteger b = curve.getB();
final BigInteger p = getPrime(params);
return new ECCurve.Fp(p, a, b, order, BigInteger.valueOf(coFactor));
}
private static BigInteger getPrime(final ECParameterSpec params) {
if (params == null) {
throw new IllegalArgumentException(
"Los parametros no pueden ser nulos" //$NON-NLS-1$
);
}
final EllipticCurve curve = params.getCurve();
final ECField field = curve.getField();
if (!(field instanceof ECFieldFp)) {
throw new IllegalStateException(
"Solo se soporta 'ECFieldFp' y se proporciono " + field.getClass().getCanonicalName() //$NON-NLS-1$
);
}
return ((ECFieldFp)field).getP();
}
private static ECParameterSpec mapNonceGMWithECDH(final BigInteger nonceS,
final ECPoint sharedSecretPointH,
final ECParameterSpec params) {
// D~ = (p, a, b, G~, n, h) where G~ = [s]G + H
final ECPoint generator = params.getGenerator();
final EllipticCurve curve = params.getCurve();
final BigInteger a = curve.getA();
final BigInteger b = curve.getB();
final ECFieldFp field = (ECFieldFp)curve.getField();
final BigInteger p = field.getP();
final BigInteger order = params.getOrder();
final int cofactor = params.getCofactor();
final ECPoint ephemeralGenerator = add(multiply(nonceS, generator, params), sharedSecretPointH, params);
if (!toBouncyCastleECPoint(ephemeralGenerator, params).isValid()) {
LOGGER.warning("Se ha generado un punto invalido"); //$NON-NLS-1$
}
return new ECParameterSpec(new EllipticCurve(new ECFieldFp(p), a, b), ephemeralGenerator, order, cofactor);
}
private static ECPoint multiply(final BigInteger s, final ECPoint point, final ECParameterSpec params) {
final org.spongycastle.math.ec.ECPoint bcPoint = toBouncyCastleECPoint(point, params);
final org.spongycastle.math.ec.ECPoint bcProd = bcPoint.multiply(s);
return fromBouncyCastleECPoint(bcProd);
}
private static ECPoint fromBouncyCastleECPoint(final org.spongycastle.math.ec.ECPoint point) {
final org.spongycastle.math.ec.ECPoint newPoint = point.normalize();
if (!newPoint.isValid()) {
LOGGER.warning("Se ha proporcionaod un punto invalido"); //$NON-NLS-1$
}
return new ECPoint(
newPoint.getAffineXCoord().toBigInteger(),
newPoint.getAffineYCoord().toBigInteger()
);
}
private static ECPoint add(final ECPoint x, final ECPoint y, final ECParameterSpec params) {
final org.spongycastle.math.ec.ECPoint bcX = toBouncyCastleECPoint(x, params);
final org.spongycastle.math.ec.ECPoint bcY = toBouncyCastleECPoint(y, params);
final org.spongycastle.math.ec.ECPoint bcSum = bcX.add(bcY);
return fromBouncyCastleECPoint(bcSum);
}
private static org.spongycastle.math.ec.ECPoint toBouncyCastleECPoint(final ECPoint point, final ECParameterSpec params) {
final org.spongycastle.math.ec.ECCurve bcCurve = toBouncyCastleECCurve(params);
return bcCurve.createPoint(point.getAffineX(), point.getAffineY());
}
}