Este post es producto de no leerse la documentación completamente. Lección #1: leer los documentos a fondo, o por lo menos no dejarlo a medias cuando te crees que ya has acabado.

Publicar una solución ISV

Uno de los pasos cuando quieres publicar una solución ISV (un vertical) es generar la licencia para los clientes. Esta licencia se tiene que firmar usando un certificado Authenticode que activará la solución, limitará el número de usuarios o definirá una fecha de caducidad.

Si comrpobamos la documentación veremos que el certificado tiene que cumplir con unos requisitos mínimos:

  • Certificado Authenticode (X.509) de una entidad certificadora (CA).
  • El tamaño de la clave debe ser de 1024-bit o 2048-bit. Las claves de 4096-bit no están soportadas.

Y el idiota que escribe esto dejó de leer ahí, contactó con el reseller de certificados y pidió un certificado CodeSign con los requisitos de arriba. Pasadas unas semanas llegó el token USB y descargué el certificado.

Ya estaba listo para finalizar los desarrollos de las soluciones y generar las licencias, así que continué leyendo los docs hasta que llegué a la parte “Certificate import and export“ y vi esto:

  • Personal Information Exchange (PFX, also known as PKCS #12) – The PKCS #12 format, which uses the .pfx file name extension, supports secure storage of certificates, private keys, and all certificates in a certification path. The PKCS #12 format is the only file format that can be used to export a certificate and its private key.

Y lo mejor de todo: la finalidad de un eToken USB es proteger la clave privada por hardware, lo que hace que exportar la clave privada sea imposible! Soy un idiota con una clave privada cara en un USB!

No pienso comprar otro certificado

Después de contactar con el reseller y la CA y confirmar que no se puede exportar la clave privada empecé a buscar una alternativa.

¿Cómo se crea una licencia? Usando el comando axutil que ya existía en AX2012 para gestionar modelos. El ejecutable está en la carpeta bin de la PackagesLocalDirectory de las VMs, y ahí hay una librería llamada AXUtilLib.dll también.

Probablemente lo que he hecho no esté permitido por la licencia, pero las herramientas disponibles no solucionaban mi problema y he tenido que crearme las mías🤷‍♂️

Tiré pa’lante, desensamblé la DLL y empecé a mirame las clases. El ejecutable espera un parámetro certificatepath, esto debería ser la clave privada en PFX, pero no la tengo.

En la DLL tenemos una clase llamada AXUtil que tiene el siguiente método GenerateLicense:

public bool GenerateLicense()
{
    return new LicenseGenerator(this.config.LicenseInfo, this.context).GenerateLicense();
}

El parámetr this.Config.LicenseInfo que se pasa incluye los argumentos de la línea de comandos. Así que en vez de usar la ruta del certificado hago esto:

public bool GenerateLicense()
{
    X509Store store = new X509Store("My", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
    X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificate Select", "Select a certificate from the following list to sign the license", X509SelectionFlag.SingleSelection);

    if (scollection.Count == 0)
    {
        throw new System.NullReferenceException("No certificate loaded.");
    }

    foreach (X509Certificate2 x509 in scollection)
    {
        X509Certificate2 certificate = x509;

        return new LicenseGenerator(this.config.LicenseInfo, this.context).GenerateLicense(certificate);
    }

    return false;
}

Esto nos permite seleccionar el certificado a usar desde nuestros certificados instalados y pasárselo al GenerateLicense del LicenseGenerator. Este método no acepta parámetros, pero gracias a la sobrecarga de métodos de C# puedo hacer esto:

internal bool GenerateLicense()
{
    X509Certificate2 certificate = this.LoadCertificate();
    if (certificate == null)
        return false;
    return this.GenerateLicenseFile(this.GenerateSignature(certificate), certificate);
}

internal bool GenerateLicense(X509Certificate2 usbCertificate)
{
    X509Certificate2 certificate = usbCertificate;
    if (certificate == null)
        return false;
    return this.GenerateLicenseFile(this.GenerateSignature(certificate), certificate);
}

Y así usar mi método. Aparte de esto sólo he tenido que cambiar una cosa más de la librería AXUtilLib original: poner los setters de la LicenseInfo como públicos.

ISVLicenseGenerator

He creado una herramienta gráfica que hace uso de esta librería modificada y nos permite crear una licencia válida con un eToken USB:

El código fuente de esta herramienta (y la DLL modificada) y un ejecutable listo para usar se pueden descargar de GitHub, y podéis hacer lo que queráis con ello. Sólo espero no recibir ninguna notificación de Microsoft…

Como podéis ver todo lo que hay que hacer es seleccionar la ruta de la licencia y completar los datos, como se hace con la herramienta normal por línea de comandos.

Hacemos clic en «Generate» y aparecerá el diálogo de selección de certificado:

Después de seleccionarlo el software del USB nos pedirá la contaseña:

¡Y listo! Tenemos una licencia válida y lista para desplegar en los entornos del cliente:

Espero que mi estupidez le sirva de ayuda a alguien!

¡Suscríbete!

Recibe un correo cuando se publique un nuevo post
Author

Microsoft Dynamics 365 Finance & Operations technical architect and developer. Business Applications MVP since 2020.

Write A Comment

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

ariste.info