miércoles, 6 de febrero de 2013

Enseñando licenciamiento de software con .NET a mi abuela

Mi abuela es una señora muy adelantada a su tiempo y con muchas inquietudes sobre las nuevas tecnologías. No hace mucho le dio por empezar a programar en C# y a veces las dudillas que le surgen me las pregunta.

El otro día tuvimos una interesante conversación sobre el licenciamiento y me he decidido a intentar transcribir nuestra conversación en una entrada. Puede ser un poco larga pero de seguro que merece la pena.

- Pues mira nieto, el otro día hice una aplicación para gestionar residencias de ancianos. Parece que tiene buena acogida y que gusta, pero con el esfuerzo que me ha supuesto, no quisiera que se pusieran a copiársela entre las residencias. Que esos son muy listos para aprovecharse de los pobres ancianos. ¿Cómo puedo hacer para evitar que se lo copien mi aplicacioncita de unos a otros?

- Claro que si abuela. Me imagino lo mal que se tiene que sentir uno al encontrarse sus cosas en el emule, torrents o RapidShares de turno…

- Niño, ¿de que me hablas? Yo me refería que se lo mandan por mail o los copian en CDs…

- Claro que si abuela. De hecho .NET tiene modelos que te permiten proteger, o mejor dicho, licenciar tus aplicaciones de manera muy sencilla y flexible. Si quieres te explico cómo se hace.

- Claro que si nieto. Pero explícamelo sencillito que tu ya sabes que a mi me cuesta un poco con estas edades…

- Bueno, bueno. A ver abuela. ¿Sabes de lo que hablo si digo atributos de clase?

- Pues no estoy del todo segura. Anda, recuérdamelo. Y mejor si es con un ejemplillo.

- Bueno abuela. Los atributos de clase son unos modificadores que se colocan justo antes de definir las clases. Y se colocan entre corchetes. Hay muchos atributos de clase, de hecho, puedes usar varios para una misma clase. Por ejemplo, este atributo sirve para indicar que una clase es serializable.

   1:  [Serializable]
   2:  class Foo
   3:  {
   4:     //...
   5:  }



Como ya te he comentado, abuela, hay muchos más. Pero el que a nosotros nos interesa para el licenciamiento se llama System.ComponentModel.LicenseProvider.


- O sea, que las protecciones se hacen usando ese atributo de clase, ¿no?


- Exactamente abuela. Cada vez que usemos este atributo en una clase, significará que esta estará protegida por una licencia.


- Vale. Entonces la clase que yo quiera proteger debe quedar más o menos así, ¿no?


   1:  [System.ComponentModel.LicenseProvider]
   2:  public class Foo
   3:  {
   4:     // ...
   5:  }



Bien, nietecito. Hasta aquí lo entiendo. Aunque me temo que lo que sigue a partir de ahora será más complicado, ¿verdad?


- Que va abuela. Mira. Verás que sencillo. Cuando una clase viene con ese atributo, significa que va a recibir un objeto licencia. Esta licencia es una instancia de la clase System.ComponentModel.License y es muy sencillita:


   1:  namespace System.ComponentModel
   2:  {
   3:      public abstract class License : IDisposable
   4:      {
   5:          protected License();
   6:          public abstract string LicenseKey { get; }
   7:          public abstract void Dispose();
   8:      }
   9:  }



Lo interesante de esta clase es que es abstracta, que implementa la interfaz IDisposable y su propiedad LicenseKey es de sólo lectura.


- Ya veo. Entonces voy a recibir una instancia de "algo" que hereda de System.ComponentModel.License. Porque como dices, es una clase abstracta. Lo que no me queda claro es quién crea esa instancia ni cómo lo hace.


- Ahora vamos a eso, abuela. El sistema de licenciamiento que tiene .NET funciona de la siguiente manera: La clase que quieres proteger va a recibir un objeto que hereda de License. Este objeto License es generado por una entidad proveedora de licencias a través del gestor de licencias que tiene el framework de .NET.


Explicado de otra manera. Cuando la clase que quieres licenciar se esta creando, le pide al gestor de licencias (System.ComponentModel.LicenseManager) una licencia. Este entregará dicha licencia haciendo uso del generador o proveedor de licencias (System.ComponentModel.LicenseProvider)


- O sea,que LicenseManager va a retornar la licencia que a su vez retorne LicenseProvider a la clase a proteger. Aquí hay gato encerrado o hay algo que todavía no me has contado. La clase License es abstracta, asi que me falta una clase License que en cualquier caso, parece que siempre la devuelve LicenseProvider. Con lo cual, no veo que hay de bueno en todo esto.


- Claro abuela, te falta añadirle toda la lógica del sistema de licenciamiento. El framework de .NET te da la base y tu la completas.


- Ay madre, que ya me asustas. ¿Cómo es eso de que yo tengo que poner la lógica a todo esto?


- Que no es para tanto, abuela. Lo que hay que hacer es crearse una clase que permita devolver las licencias que queramos usando la lógica que necesitemos. Y por supuesto, la licencia que esta devuelva podrá ser como queramos siempre y cuando herede de System.ComponentModel.License.


La clase generadora de licencias se le pasará como argumento al atributo de clase System.ComponentModel.LicenseProvider. De manera que el proveedor pueda invocar a nuestra clase generadora para obtener la licencia correspondiente.


- Oye. ¿Y es muy difícil construir una clase generadora de licencias de estas? Me vas a tener que enseñar un ejemplo de esto que me estás contando, ¿eh?


- Bueno, tan difícil como queramos hacerlo nosotros. Microsoft de hecho nos dejó una creada muy sencillita. De hecho es tan sencillita que se aconseja no usarla para nuestros desarrollos. Esta clase es System.LicenseProvider.LicFileLicenseProvider.


Te enseño un ejemplo de cómo se usaría este generador:


   1:  [System.ComponentModel.LicenseProvider(typeof(System.ComponentModel.LicFileLicenseProvider))]
   2:      public class Foo
   3:      {
   4:          private System.ComponentModel.License _license = null;
   5:   
   6:          public Foo()
   7:          {
   8:              _license = System.ComponentModel.LicenseManager.Validate(this.GetType(), this);
   9:          }
  10:      }



Concretamente LicFileLicenseProvider funciona de la siguiente manera:


1. En la carpeta donde se encuentra el ensamblado con la clase a proteger, busca un fichero con extensión .lic y nombre igual que el nombre del namespace con la clase a proteger, un punto y el nombre de la clase a proteger.


2. El fichero .lic debe tener el siguiente contenido de texto: "Namespace+clase is a licensed component.", donde Namespace + clase es el nombre del namespace de la clase que queremos proteger.


- Entiendo, entiendo. Pero no veo por qué no quieres que se use ese tipo de proveedor de licencias.


- ¡Abuela! Cualquiera que supiese que estas usando ese licenciamiento puede crearse manualmente el fichero .lic y… adiós protección.


- Bueno, bueno. Por cierto, ¿qué pasa si el torpe de turno va y borra o modifica el contenido de ese fichero?


- En ese caso, la llamada en el constructor de tu clase a Validate() fallará y lanzará una excepción de tipo System.ComponentModel.LicenseException. Si no la capturas, el licenciamiento habrá funcionado, puesto que no se ha creado ninguna instancia de la clase protegida, pero posiblemente el mensaje de error que lance la aplicación no será muy descriptivo para el usuario.


Las excepciones, cuando se producen, suelen ser bastante lentas, así que si únicamente quieres saber si existe licencia, puedes usar el método IsValid, en lugar de Validate. Este te retornará un booleano indicando si la licencia es valida o no. Pero no te devolverá ninguna licencia.


- Entonces, realmente no necesito ninguna clase License. Me podría bastar con llamar a IsVaild y así saber si se puede ejecutar o no la aplicación, ¿no?


- Bueno, abuela, de eso se trata. El modelo es así de flexible. Si no lo necesitas, no lo uses. Pero fíjate lo que se puede llegar a montar. Imagínate que a pesar de no tener una licencia buena, quisieras permitir que la aplicación se ejecutara. Por ejemplo, como una versión de prueba. O que dependiendo de la licencia, se pudieran ejecutar unas u otras opciones de la aplicación.


- ¡Pues claro! Así si que sería fácil, por ejemplo, vender por separado el modulo de gestión de enfermeros y por otro lado la gestión de las medicinas… ¡Realmente interesante!


- Exacto abuela. Y en la propiedad License.LicenseKey podrías poner el identificador del cliente.


Resumiendo. Tenemos 4 actores en esto de la gestión de licencias: La clase que queremos licenciar, el tipo de licencia, el proveedor de licencias y el gestor de licencias.


- Si, lo voy entendiendo. Pero, ¿cómo podría crearme mi propio proveedor de licencias?


- No es difícil, abuela. Basta con crearse una clase que herede de LicenseProvider y otra que herede de License:


   1:  using System.ComponentModel;
   2:   
   3:  /// My License Provider class
   4:  class MyLicenseProvider : LicenseProvider
   5:  {
   6:      public override License GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
   7:      {
   8:          return new MyLicense();
   9:      }
  10:  }
  11:   
  12:  // My License class
  13:  class MyLicense : License
  14:  {
  15:      public override string LicenseKey
  16:      {
  17:          get { return "Some license key"; }
  18:      }
  19:  }



Este es un ejemplo muy sencillo. Realmente no estamos haciendo ninguna comprobación de nada. Siempre devuelve una licencia buena.


- Ya creo que me voy enterando. De todas maneras no estoy muy segura de la propiedad LicenseKey de la licencia. Si es de sólo lectura y toda la lógica para la generación de la licencia reside en el proveedor. Entonces le tendré que pasar el valor del LicenseKey a través del constructor, ¿no? Sino, no lo veo yo. Luego dependiendo de ese LicenseKey la aplicación permitiría o no hacer determinadas acciones. ¿Cierto?


Más o menos esta sería mi clase Licencia, que recibiría el License Key a través del constructor:


   1:  class MyLicense : License
   2:  {
   3:      private string _licenseKey = null;
   4:   
   5:      public MyLicense(string licenseKey)
   6:      {
   7:          _licenseKey = licenseKey;
   8:      }
   9:   
  10:      public override string LicenseKey
  11:      {
  12:          get { return _licenseKey; }
  13:      }
  14:   
  15:      public override void Dispose()
  16:      {
  17:      }
  18:  }



Mi clase Proveedora de licencias que, a modo de ejemplo, devuelve una licencia siempre con la misma clave:


   1:  using System.ComponentModel;
   2:   
   3:  class MyLicenseProvider : LicenseProvider
   4:  {
   5:      public override License GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
   6:      {
   7:          return new MyLicense("Full license");
   8:      }
   9:  }




Y por último mi clase protegida. En este caso quiero proteger la llamada a DoSomething chequeando el valor de la clave de licencia:


   1:  using System.ComponentModel;
   2:   
   3:  [LicenseProvider(typeof(MyLicenseProvider))]
   4:  public class Foo
   5:  {
   6:      private License _license = null;
   7:   
   8:      public Foo()
   9:      {
  10:          _license = LicenseManager.Validate(this.GetType(), this);
  11:      }
  12:   
  13:      public void DoSomething()
  14:      {
  15:          if (_license.LicenseKey != "Full license")
  16:          {
  17:              throw new LicenseException(this.GetType(), this, 
  18:                             "You don´t have a valid license to invoke this method.");
  19:          }
  20:   
  21:          // Do something here ...
  22:      }
  23:  }



- Bueno abuela, claro que lo podrías hacer así. Pero te estarías cargando toda la filosofía del modelo de licencias. Si en cualquier momento quisieras cambiar la forma de proteger tu aplicación, tendrías que modificar en todos los sitios de la clase protegida donde se comprueba el valor de la clave de licencia.


Realmente a la aplicación, o en tu ejemplo, a la clase protegida, le da absolutamente igual el valor de LicenseKey. El hecho de que se haya podido llamar a la función ya significa que hay una licencia valida pues el constructor ya la ha validado.


- Ah! Entonces también la lógica de saber si las claves de licencia son buenas o no, de si se puede ejecutar cierta función y demás, se hace también en el proveedor. ¿Cierto?


- Exactamente. Piensa en tu licencia como un contrato o un permiso de utilización de la clase protegida. Si tiene licencia, funciona, sino, ni siquiera puede existir. Además, si fuera necesario, podría indicar determinadas limitaciones.


- Comprendo, comprendo. Es como tu carnet de conducir, ¿no? Si tienes licencia, puedes llevar el coche, y si no, no. Pero además, tu carnet también te limita los vehículos que puedes conducir, ¿verdad?


- Eso es abuela, muy buen ejemplo. Me lo tengo que apuntar para cuando se lo tenga que explicar a otras personas. Jejeje.


- Entonces el ejemplo que he dicho antes no sirve. Mejor sería poner algo así:


Mi licencia es capaz de indicar si se está en modo demostración. En este modo habrá operaciones que no se pueden ejecutar:


   1:  class MyLicense : License
   2:  {
   3:      private string _licenseKey = null;
   4:   
   5:      public MyLicense(string licenseKey, bool isDemoVersion)
   6:      {
   7:          _licenseKey = licenseKey;
   8:          this.IsDemoVersion = isDemoVersion;
   9:      }
  10:   
  11:      public override string LicenseKey
  12:      {
  13:          get { return _licenseKey; }
  14:      }
  15:   
  16:      public bool IsDemoVersion { get; private set; }
  17:   
  18:      public override void Dispose()
  19:      {
  20:      }
  21:  }



Mi clase proveedora de licencias determina si la licencia a generar es de tipo demostración o no. En mi ejemplo, simplemente es de demostración a partir de 2013:


   1:  class MyLicenseProvider : LicenseProvider
   2:  {
   3:      public override MyLicense GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
   4:      {
   5:          // Demo version since 2013
   6:          if (DateTime.Now.CompareTo(new DateTime(2013, 1, 1)) > 0)
   7:          {
   8:              return new MyLicense("Full license", true);
   9:          }
  10:          else
  11:          {
  12:              return new MyLicense("Full license", false);
  13:          }
  14:      }
  15:  }



Por último, la clase que estoy protegiendo ya no tiene que tener ninguna lógica respecto a la licencia. Sólo chequear que no está en modo demostración:


   1:  [LicenseProvider(typeof(MyLicenseProvider))]
   2:  public class Foo
   3:  {
   4:      private MyLicense _license = null;
   5:   
   6:      public Foo()
   7:      {
   8:          _license = LicenseManager.Validate(this.GetType(), this) as MyLicense;
   9:      }
  10:   
  11:      public void DoSomething()
  12:      {
  13:          // Demo version cannot execute this code!!!
  14:          if (_license.IsDemoVersion)
  15:          {
  16:              throw new LicenseException(this.GetType(), this, 
  17:                             "You don´t have a valid license to invoke this method.");
  18:          }
  19:   
  20:          // Do something here ...
  21:      }
  22:  }



- Mucha mejor pinta, abuela. Tampoco podemos olvidar que la llamada a Validate nos ofrece otros argumentos la mar de interesantes. Vamos a verlos.



  • LicenseContext context. Este objeto nos da el contexto en el cual se va a usar la clase a proteger. Quizás su propiedad más importante sea UsageMode, el cual nos va a permitir saber si estamos ejecutando una aplicación normal de escritorio o bien si estamos usando la clase en modo diseño dentro de Visual Studio. Por ejemplo, para licenciar componentes o controles gráficos para otros desarrolladores.
  • Type type. Nos da el tipo de la clase a licenciar. Así podemos usar el mismo proveedor de licencias para proteger varias clases.
  • object instance. Si no fuese sufieciente saber el tipo, tambien tenemos a nuestra disposición la instancia de la clase a proteger.
  • bool allowExceptions. Nos indica si debemos lanzar una excepción LicenseException en caso de no cumplir con la licencia. Y esto tiene sentido pues acuerdate de la llamada IsValid que no lanza excepciones. Básicamente Validate e IsValid usan la llamada a esta función. Una permite excepciones y la otra no.

Sin meternos en historias de dar soporte a distintos tipos de clases a proteger ni nada de eso, tu clase proveedora de licencias quedaría mejor así:


   1:  class MyLicenseProvider : LicenseProvider
   2:  {
   3:      public override MyLicense GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
   4:      {
   5:          // Full license until 2013
   6:          if (DateTime.Now.CompareTo(new DateTime(2013, 1, 1)) < 0)
   7:          {
   8:              return new MyLicense("Full license", false);
   9:          }
  10:          // Demo version between 2013 - 2014
  11:          else if (DateTime.Now.CompareTo(new DateTime(2014, 1, 1)) < 0)
  12:          {
  13:              return new MyLicense("Demo version", true);
  14:          }
  15:          // No license from 2014
  16:          else
  17:          {
  18:              if (allowExceptions)
  19:              {
  20:                  throw new LicenseException(type, instance, "Invalid license.");
  21:              }
  22:              else
  23:              {
  24:                  return null;
  25:              }
  26:          }
  27:      }



Y así nuestra clase protegida puede usar IsValid en lugar de Validate sin preocuparse de si se van a producir excepciones.


Es muy  importante que te quede claro que toda la lógica de la licencia debe quedar dentro de la clase de generadora de licencias. Esta puede hacer todo lo que se te ocurra. Desde leer un fichero de texto como hace LicFileLicenseProvider, hasta conectarse a un servidor remoto en otro continente para obtener la licencia.


- Pues si nietecito, ya tengo más o menos claro todo esto de las licencias. Ya puedo estar segura de que nadie se va a copiar mi programa de residencias de ancianos.


- Me alegro de haberte ayudado abuela. De todas maneras, siempre habrá alguien que encuentre la manera de saltarse la licencia. Pero todo dependerá de lo importante que sea lo que tengamos que proteger y el tiempo que le queramos dedicar poniendo trampas y demás dificultades a estos individuos. Recuerda, no hace falta crear la super licencia que protege los sistemas del Pentágono. La mayoría de las personas lo dará por imposible sólo al ver que ya tiene licencia. Y eso es lo importante.

11 comentarios:

  1. Hola me puede colaborar en enviarme los fuente de este ejercicios para ponerlos enpracticas

    ResponderEliminar
  2. El Proveedor: Copio sus ejecutables o Instalo la aplicación y le asigno al licencia a una PC.
    El Pirata: Copia toda la carpeta incluida el archive de licencia y se lo lleva a otra empresa, entonces para que sirviria este tipo de licenciamiento.

    Lo ideal seria licenciar solo para una PC para que realmente sea licenciamiento porque sino el ejemplo NO aplicaria.

    Espero tus comentarios

    Gracias.

    ResponderEliminar
    Respuestas
    1. Hola
      Bueno, por partes.
      Normalmente si copio la carpeta de una aplicación instalada y la pego en otro ordenador, la mayoría de las veces no va a funcionar. Normalmente el instalador hará más cosas que copiar los ficheros en la carpeta de destino: Escribir en el registro de Windows, registrar ensamblados en el GAC, etc.

      Pero supongamos que el instalador sólo copie los ficheros a una carpeta de destino. Que creo que es tu duda.
      La clave es lo que haga tu clase heredada de LicenseProvider en el método GetLicense()
      Como explico, este puede desde leer un fichero hasta conectarse a un servidor remoto.
      Si uso un fichero para saber la licencia, no tiene sentido que este vaya cno el instalador, sino que será la aplicación, una vez que se empiece a ejecutar el que le pregunte al usuario por los datos de licenciamiento y cree un fichero con el contenido.
      Puedes pensar que entonces un pirata podría coger este fichero y copiarlo a otro PC y así la aplicación instalada pensaría que ya ha sido licenciada. Pero claro, ahi está tu habilidad para ocultar qué es exactamente el contenido de ese fichero y cómo lo interpreta to LicenseProvider.
      Ni que decir tiene que esta clase debería ser ofuscada, pues sino sería sencillo que un pirata pueda ver qué hace esta clase para así saltarse la protección.

      Un ejemplo bastante común es incluir dentro del fichero de licencia información unica del PC. Por ejemplo la dirección MAC de la tarjeta de red, identificador del disco duro o del procesador del PC. De esta manera podemos saber si el fichero corresponde con el PC donde fue generado.

      Espero haberte ayudado a resolver tu duda.

      Un saludo

      Eliminar
  3. Como puedo hacer para que mi licencia sea por un tiempo definido ejemplo 1 o 2 meses, pero tener en cuenta si la hora de la pc la cambian???

    ResponderEliminar
    Respuestas
    1. Ese s un tema bastante complicado. Hay miles de trucos por ahí para saber si se ha modificado la fecha de sistema. Lo mejor sería no usar la fecha del PC, pues es muy sencillo cambiarla y las aplicaciones que hagas dificilmente pueden saber si se ha cambiado una fecha.
      Si la aplicación tiene acceso a internet puedes usar cualquier servicio de fecha. Yo he usado incluso llaves USB de protección con reloj integrado para que no me engañen con la fecha.
      Si no te queda más remedio que usar la fecha de PC puedes intentar grabar la fecha de último uso de la aplicación. Si se da el caso de que el último uso fue en el futuro, pues... alguien ha hecho trampa.

      Eliminar
  4. Gracias Oscar Arrivi, pero en realidad lo que me interesaría es que no se utilice la aplicación después del tiempo que defina, ademas el usuario inteligentemente podría mantener la hora entre el día de instalación y el día en que supuestamente se vence. La aplicación no tendrá acceso a Internet así que tendré que utilizar la de la pc. Habrá alguna otra forma de hacer esto???

    ResponderEliminar
    Respuestas
    1. Ya te digo que es complicado pues Windows sólo mantiene la fecha del sistema que tienes con el reloj y cualquiera la puede cambiar.
      No se si te he entendido bien. Supuestamente al arrancar la aplicación, lo primero que haces es comprobar la fecha. Si la fecha ha expirado, la aplicación termina con un mensaje indicandolo.
      Claro. En ese momento el usuario puede modificar la fecha para que al volver a ejecutar pase la comprobación. Pero cómo ya anteriormente escribiste la fecha y la nueva es anterior. Caería en la trampa. Así que la aplicación tambien terminaría.

      No se si es esto a lo que te refieres. No se si te has planteado otra opción que es por número de usos en lugar de por tiempo. Es más sencillo.

      Mi opinión respecto a la protección de software es que no se puede hacer la protección perfecta. Es imposible. Y contra mejor es, más esfuerzo requiere implementarla. Mi consejo es poner en una balanza lo seguro que queremos que sea el programa con el tiempo que queramos dedicarle a su implementación. En la mayoría de los casos, los usuarios normales (no expertos) se dan por vencidos a la primera prueba y o bien pagan la licencia, o buscan otro programa con parecidas capacidades. Si alguien quiere pirateartelo, con tiempo, lo conseguirá. Es inevitable.

      Eliminar
  5. hola oscar como ya te han preguntado .. TIenes los codigos fuentes??? seria bueno tener un video tutorial en youtube ;).. (sin intervencion de tu abuela jajajja)...
    '
    tengo aplicaciones mvc web como haria para colocarle proteccion de licencia a algo asi, aparte de obfuscar el codigo??

    LicFileLicenseProvider mensionaste que crea un archivo lic, pero este archivo es muy facil de crackear, tambien mensionaste como realizar un servicio web para comprobar fechas y si es un usuario legal?

    ResponderEliminar
  6. he usado archivos encriptados con informacion de la placa base, este metodo protege el software si se copian todos los archivos.

    ResponderEliminar
    Respuestas
    1. Hola Alan, buen día! Qué clase utilizaste para obtener información de la placa base y demás? Tengo que hacer algo similar y quería combinarlo con lo que mostró Oscar acá. Gracias!!

      Eliminar