miércoles, 19 de diciembre de 2007

Instrucciones de procesador (I)

Uno de mis eternos proyectos, que retomo una y otra vez y que nunca termino, es diseñar y programar el emulador de un microprocesador.

El núcleo del proyecto de programación es el lector de instrucciones, y eso nos lleva a que una pieza fundamental del diseño es el juego de instrucciones.

Una de las operaciones fundamentales de un procesador es, evidentemente, leer un valor de la memoria y depositarlo en un registro o viceversa, escribir en la memoria un valor presente en un registro.

El diseño de procesador que he elegido implica que estas dos fundamentales operaciones se realicen únicamente desde el registro que he denominado A.

A estas operaciones les he asignado los mnemónicos LEE y SCR y los códigos binarios 0xc0 y 0xc1. Ambas operaciones necesitan como argumento una dirección de memoria.

Asumiendo que el procesador emulado es una estructura de datos que almacenamos en procesador, que la memoria es una matriz que almacenamos en memoria, y que el puntero de instrucción es el registro CI del procesador, el código de emulación para estas instrucciones lo he programado como sigue:

switch(memoria[chip->CI])
{
case 0xc0 /* Instrucción LEE */
chip->A=memoria[memoria[chip->CI+1]];
break;
case 0xc1 /* Instrucción SCR*/
memoria[memoria[chip->CI+1]]=chip->A;
break;
/* Aquí el código para otras instrucciones */
}


Con este código, una instrucción de dos bytes como 0xc0 0x12 se desensambla como LEE 0x12, es decir, "coge el contenido de la posición de memoria 0x12 y cópialo en el registro A".

Continuaré más adelante...

viernes, 2 de noviembre de 2007

Interprogramación de Fortran y C

Estoy en Valencia encargado de mantener código.

Código en Fortran.

El problema de los códigos heredados, en general, es que son heredados. Uno no cuenta con sus propias costumbres de programación (indentado, nombrado de variables, comentarios en el código...) sino que tiene que adivinar sobre código ajeno.

Pero bueno, una cosa es eso, y otra Fortran. Fortran como lenguaje, tal y como se utiliza en Ciencia (códigos FORTRAN 77 heredados de estudiantes de doctorado en estudiantes de doctorado) es algo realmente asqueroso. A veces da la tentación de reprogramar en C, o de modernizar el código a Fortran 95.

La almendra de la cuestión es hacer esa reprogramación en C de rutinas que seguirán siendo llamadas desde Fortran, al estilo Fortran.

¿Funciones o subrutinas?



En C tenemos funciones. En Fortran tenemos funciones y subrutinas. Si tenemos que llamar a Fortran desde C, ¿cómo escribir en Fortran el código llamado?

Básicamente, si nuestra función en C retorna un valor, en Fortran usaremos una función. Si la función en C no retorna (void), en Fortran usaremos una subrutina.

Y viceversa, si en Fortran tenemos una llamada CALL, en C usaremos una función void, mientras que si en Fortran tenemos una llamada a función, en C usaremos una función del mismo tipo.

Nombres de funciones



Normalmente los compiladores de Fortran, en las tablas de símbolos de los ficheros objeto, cambian el nombre de los objetos a minúsculas (recordemos que en Fortran las mayúsculas y las minúsculas son equivalentes) y añaden un guión bajo al final del nombre.

Así, si desde C queremos llamar a una función o subrutina Fortran, debemos llamarla con su nombre escrito completamente en minúsculas y añadir un guión bajo al final.

Y si desde Fortran queremos llamar a una función C, ésta tiene que tener un nombre completamente en minúsculas y un guión al final, que en el código Fortran no pondremos. Si no es así, nos veremos obligados a programar en C un wrapper que siga esta convención y llame a la función C real.

Paso de variables



Es importante recordar que en Fortran las variables siempre se pasan por referencia, nunca por valor. Incluso si llamamos a una función con argumentos constantes.

Por ello, las funciones C que sean llamadas desde Fortran deben esperar que todos sus argumentos sean punteros, y las funciones o subrutinas Fortran que sean llamadas desde C deben ser llamadas proporcionándoles argumentos que sean punteros.

Hay que tener en cuenta, cuando se pasen matrices, que el nombre de la matriz, a secas, ya es un puntero en C, así que no hay que añadirle el asterisco delante.

Almacenamiento de matrices



En C las matrices se almacenan "por filas", o mejor expresado, el índice más a la derecha, el último, recorre secuencialmente los elementos de la matriz. En Fortran, en cambio, las matrices se almacenan "por columnas", es decir, el índice más a la izquierda, el primero, es el que recorre secuencialmente los elementos.

Por ello, cuando pasamos una matriz de Fortran a C o viceversa, debemos invertir el orden de todos sus índices.

También hay que tener en cuenta que mientras que en Fortran una matriz puede declararse con índices arbitrarios, que por defecto comienzan en 1, en C las matrices siempre comienzan con el índice 0.

Por la misma razón, todo programador decente de Fortran escribe bucles anidados en los que el bucle más interno recorre el primer índice, mientras que todo programador decente de C escribe bucles anidados en los que el bucle más interno recorre el último índice.

Bloques COMMON



En Fortran es común utilizar bloques COMMON como un modo de que todas las subrutinas y funciones puedan acceder a determinadas variables. Es posible hacer uso desde C de estos bloques COMMON, si tenemos en cuenta que se presentan como estructuras de datos cuyo contenido es el conjunto de variables que en C correspondan a las variables Fortran.

Es necesario tener en cuenta que lo mencionado acerca de los identificadores de funciones Fortran en C también se aplica a los nombres de estas estructuras y a los de las variables que las componen.

Es necesario fijarse con cuidado, si tenemos problemas de acceso a estas variables, en las direcciones resultantes para las variables flotantes de doble precisión. Los compiladores de C pueden añadir relleno en las estructuras de datos para que los límites de las variables se alineen correctamente, y los de Fortran no pueden hacerlo en sus bloques COMMON.

sábado, 15 de septiembre de 2007

Conectarse a redes inalámbricas

Acabo de conectarme por primera vez a una red inalámbrica con cifrado WPA. No es difícil, si sabes cómo hacerlo, y yo no sabía. Pero en cuanto miré en Internet desde otro ordenador que ya estaba conectado, vi que tenía que usa wpa_supplicant, que por suerte ya tenía instalado. Cosas de las distros modernas.

Bastaron luego 5 minutos de buceo en las páginas de manual correspondientes, y saqué un guión que me hace el trabajo.

Pero bueno, antes de hablar del guión para WPA, hablaré del guión para WEP, que es más sencillo.

El guión para WEP



Este guión supone que nos vamos a conectar a una red inalámbrica cifrada mediante WEP, usando una tarjeta de red inalámbrica que nuestro ordenador llama eth1 (también podía ser wlan0). También supone que tenemos un servidor de nombres instalado en el equipo, algo que yo siempre hago, salvo que necesite un servidor de la red local para resolver nombres locales.

La clave de la red WEP es 01:23:45:67:89:AB:CD:EF:01:23:45:67:89 (por ejemplo) y transmite en el canal 9 (por poner). Daremos a nuestro equipo la dirección IP 192.168.1.58 (por dar una) ya que la dirección IP del punto de acceso es 192.168.1.1 (como podía ser otra).

# /usr/local/sbin/conf_wep
#Configuración inalámbrica de la tarjeta
iwconfig eth1 key restricted 01:23:45:67:89:AB:CD:EF:01:23:45:67:89 channel 9 ap auto
#Configuración IP
ifconfig eth2 192.168.1.58
#Borramos la ruta por defecto, por si la tarjeta eth0 (la de cable) había establecido una y asignamos la que corresponde
route del default
route add default gw 192.168.1.1
#Configuramos el resolvedor de nombres para indicarle que es el equipo propio, y lo arrancamos si estaba parado.
echo nameserver 127.0.0.1 > /etc/resolv.conf
/etc/init.d/bind9 restart


El guión para WPA



El guión para WPA es muy parecido al de WEP, solo que además hay que configurar wpa_supplicant. Esto lo hacemos primero, en un fichero que podemos llamar, por ejemplo, /etc/wpa_supplicant.conf (aunque podemos tener varios, si solemos conectar el portátil a varias redes WPA distintas):

# /etc/wpa_supplicant.conf
network={
ssid="Mi Red WPA"
psk="La clave de mi red WPA"
}


Luego, la configuración de la tarjeta es muy similar, solo que en vez de proporcionar directamente los parámetros inalámbricos le dejamos ese trabajo a wpa_supplicant:

# /usr/local/sbin/conf_wpa
#Configuración inalámbrica de la tarjeta
wpa_supplicant -B -ieth1 -c/etc/wpa_supplicant.conf
ifconfig eth2 192.168.1.58
route del default
route add default gw 192.168.1.1
echo nameserver 127.0.0.1 > /etc/resolv.conf
/etc/init.d/bind9 restart


Y ya está, a trabajar.

viernes, 17 de agosto de 2007

Hotplug en Xen

Xen es un sistema de paravirtualización (enlace en inglés), es decir, una manera de correr varios sistemas operativos en una máquina física, cada sistema creyendo que corre en una máquina real cuando lo hace en una máquina virtual con las características que hayamos definido.

Es paravirtualización, y no virtualización, porque los sistemas que van a correr en las máquinas virtuales deben ser modificados para correr sobre Xen. No obstante, las últimas versiones de Xen ofrecen virtualización real si se utilizan sobre procesadores de última generación, con instrucciones específicas.

El caso es que la máquina virtual no utiliza discos duros reales, sino elementos que desde la máquina real se le exportan como tales. Dichos elementos pueden ser un disco real, una partición o un fichero, y desde la máquina virtual se verán como un disco duro o una partición.

Entre las virtudes de Xen está la posibilidad de añadir o eliminar tales elementos de la máquina virtual en caliente, como si se enchufaran o desenchufaran de ella.

Y ahora, la almendra del asunto: Quiero que cuando enchufe un dispositivo en la máquina real (llamada "el hierro"), se enchufe automáticamente a la máquina virtual.

Esta solución la desarrollé yo, a partir de una idea de Kuko "Maestro Xen" Armas, cuando, teniendo otras cosas mejores que hacer en la empresa (Grupo CPD), me empeñé en sacar adelante la idea en vez de darlo por imposible.

El primer paso es detectar que se ha enchufado un dispositivo determinado al hierro. De eso se encarga udev. El segundo paso es introducir ese dispositivo en la máquina virtual. De eso se encarga xm, uno de los comandos de Xen.

Y en medio, hay que decidir a qué máquina virtual, de las varias que puede haber corriendo, se debe enchufar el dispositivo introducido. De esto se encarga XenHotplug, la solución que he desarrollado.

XenHotplug se compone de reglas de udev, guiones shell llamados desde esas reglas y un directorio de registro. Uno de esos guiones shell utiliza la firma de discos que presentaba en el artículo anterior.

Reglas de udev



Debemos crear un fichero de reglas de udev. Residirá en el directorio /etc/udev/rules.d y lo podemos llamar, por ejemplo, 10-local.rules. Ese nombre hará que se ejecute después de ciertas reglas fundamentales (las 5-early.rules) pero antes que el resto de reglas predefinidas.

En dicho fichero detectaremos la introducción o eliminación en el sistema de un dispositivo determinado y diremos qué hay que hacer con él:

SUBSYSTEM=="block", SYSFS{product}=="Mass Storage Device", \
SYSFS{manufacturer}=="Prolific Technology Inc.", \
SYSFS{start}=="63", ACTION=="add", \
PROGRAM="/usr/local/sbin/identify.sh %P", \
SYMLINK+="externalusbdisk%c", \
RUN+="/usr/local/sbin/action.sh %c %k"

SUBSYSTEM=="block", ACTION=="remove", \
RUN+="/usr/local/sbin/action.sh 0 %k"


La primera regla detecta la introducción en el sistema (ACTION=="add") de un dispositivo de bloque (SUBSYSTEM=="block") que puede ser cualquier cosa que se comporte como un disco duro externo o un disquete externo, que sea de la clase de dispositivos tipo disco (SYSFS{product}=="Mass Storage Device"), de una determinada marca (SYSFS{manufacturer}=="Prolific Technology Inc.").

Esto detectará la introducción de cualquier dispositivo de bloque, es decir, tanto el disco externo como sus particiones. Hay que tener en cuenta que al enchufar el disco, se genera un evento de introducción del disco completo, pero además el núcleo (kernel) del sistema revisa el disco y detecta sus particiones, que son añadidas al sistema, además del propio disco. Por eso, cuando enchufamos un disco particionado, aparecen los dispositivos correspondientes a las particiones, y no tenemos que crearlos a mano.

La regla, además, distingue si se trata del evento correspondiente al disco completo o a la partición, ya que la pieza SYSFS{start}=="63" sólo la cumple la primera partición. De esta manera la regla no casa con el disco completo, ni con las restantes particiones de un disco, sino sólo con la primera.

La siguiente pieza de la regla (PROGRAM="/usr/local/sbin/identify.sh %P") llama a un programa (guión de shell) de identificación del disco, que nos dirá de qué disco se trata exactamente. Así podremos distinguir los discos de esa marca y modelo que queremos que se enchufen a la máquina virtual de los que no. El programa se llama con un argumento (%P) que indica qué nombre de dispositivo se ha asignado al disco completo.

El programa proporciona un número de dispositivo, si se trata de un disco conocido (para lo que usamos la firma del disco) o la cadena NOID si se trata de un disco no identificado. En cualquiera de los dos casos, se crea un enlace simbólico /dev/externalusbdiskX (donde X es el número proporcionado antes), es decir, puede aparecer un enlace como /dev/externalusbdisk2 para el disco identificado como 2, o /dev/externalusbdiskNOID para un disco no identificado, gracias a la parte de la regla que dice SYMLINK+="externalusbdisk%c".

Finalmente, se ejecuta otro programa (guión de shell), con el dispositivo y el enlace simbólico ya creados, al que se pasan como argumentos el mismo número o NOID establecido antes y el nombre real del dispositivo (RUN+="/usr/local/sbin/action.sh %c %k"). Este programa borrará el enlace /dev/externalusbdiskNOID, si se trata de un dispositivo no identificado, o añadirá la partición a la máquina virtual y la registrará, si se trata de un dispositivo identificado.

La segunda regla detecta la retirada del sistema (ACTION=="remove") de cualquier (no podemos pedir mucha información acerca de un dispositivo ya retirado, ¿no?) dispositivo de bloque (SUBSYSTEM=="block"), y ejecuta el mismo programa que en el caso anterior, que en esta ocasión detectará si el dispositivo está registrado como insertado en la máquina virtual y, en tal caso, lo eliminará de la misma y lo desregistrará.

Guiones shell



Ahora veremos los programas que son llamados desde udev. El primero es identify.sh:

#!/bin/bash
# fichero /usr/local/sbin/identify.sh de XenHotplug

disp=$1
gen_id=`cat /usr/local/lib/identify.in`
disk_id=`dd if=/dev/$1 count=3 bs=1 skip=440 2> /dev/null`
disk_sn=`dd if=/dev/$1 count=1 bs=1 skip=443 2> /dev/null`
disk_sn_f=`echo -n $disk_sn|od -A n -N 1 --format=u`
if [ "$disk_id" = "$gen_id" ] ; then
echo $disk_sn_f
else
echo NOID
fi


Como se puede ver, el programa guarda en la variable $disp el nombre del dispositivo padre que la regla de udev le pasa como argumento, a continuación guarda en la variable $gen_id el contenido del fichero /usr/local/lib/identify.in, que es la firma de disco que empleamos para reconocerlos, y a continuación lee datos del propio disco.

Primero lee los bytes que deberían tener la firma de disco y los almacena en la variable $disk_id, y luego lee el byte de número de serie y lo almacena en la variable $disk_sn. Este número de serie, que es un byte con un valor entre 0 y 255 y que por lo tanto puede representar un carácter cualquiera, hemos de transformarlo en una cadena de texto para poderlo comparar, por eso lo formateamos como tal y lo almacenamos en la variable disk_sn_f.

Finalmente, si la firma del disco es la que esperamos, devolveremos el número de serie, que la regla udev empleará como ya hemos visto, y si el disco no es uno de los que buscamos, devolveremos la cadena NOID.

Obviamente, el contenido de /usr/local/lib/identify.in es la firma binaria que esperamos:

# hexdump /usr/local/lib/identify.in -e "\"%x\""
eeeeee


El segundo programa, action.sh, es el encargado de registrar y desregistrar los dispositivos, y de insertarlos y eliminarlos de la máquina virtual, así como de borrar el enlace simbólico /dev/externalusbdiskNOID en su caso:

#!/bin/bash
# fichero /usr/local/sbin/action.sh de XenHotplug

virt=mimaquinavirtual

if [ "$ACTION" = "add" ] ; then
if [ "$1" = NOID ] ; then
rm -f /dev/externalusbdisk$1
else
mkdir /var/xenhotplug/$1
echo $2 > /var/xenhotplug/$1/devname
/usr/sbin/xm block-list $virt > /var/xenhotplug/$1/prelist
/usr/sbin/xm block-attach $virt phy:externalusbdisk$1 hdb$1 \
w 2>&1
/usr/sbin/xm block-list $virt > /var/xenhotplug/$1/postlist
diff -u /var/xenhotplug/$1/{pre,post}list > \
/var/xenhotplug/$1/difflist
cat /var/xenhotplug/$1/difflist | grep ^+ | tail -n -1 | \
awk '{print \$1}'|awk -F + '{print \$2}' > \
/var/xenhotplug/$1/vdev
fi
else
if [ "$ACTION" = "remove" ] ; then
for file in /var/xenhotplug/* ; do
filecont=`cat $file/devname`
if [ "$filecont" = "$2" ] ; then
vdev=`cat $file/vdev`
/usr/local/sbin/assure-detach.sh $virt $vdev 2>&1
rm -rf $file
fi
done
fi
fi


El código actual no es exactamente ese, sino que incluye varias líneas de depurado y de registro que aquí no se muestran por simplicidad.


Es obvio que se trata de código aún no muy pulido, como en el guión shell anterior, no obstante hace su trabajo bastante bien. Eso sí: asume que la máquina virtual se llama mimaquinavirtual y que es la única máquina en la que insertamos dispositivos por esta vía, y asume que está permanentemente encendida.

Bueno, al grano. En primer lugar, vemos que hay dos grandes bloques de código, el primero se ejecuta si la variable de entorno $ACTION tiene el valor add y el segundo si tiene el valor remove. Esta variable de entorno la establece udev para el programa que ejecuta cuando se produce un evento.

El primer bloque, correspondiente a add, se subdivide en dos partes. Por un lado, si el programa se llama con un primer argumento de NOID, que recordemos es lo que ocurre cuando se introduce un disco no firmado, el programa borra el enlace simbólico espúreo y sale. Por otro lado, si el programa se llama con cualquier otro argumento supondremos que se trata del número de disco y entraremos en harina.

En primer lugar crearemos el directorio donde registramos este disco (mkdir /var/xenhotplug/$1), lo registramos en un fichero en ese directorio (echo $2 > /var/xenhotplug/$1/devname, hallamos la lista de dispositivos conectados a la máquina virtual (/usr/sbin/xm block-list $virt > /var/xenhotplug/$1/prelist), insertamos el disco en la máquina virtual (/usr/sbin/xm block-attach $virt phy:externalusbdisk$1 hdb$1 w 2>&1) y volvemos a hallar la lista de dispositivos conectados (/usr/sbin/xm block-list $virt > /var/xenhotplug/$1/postlist). Esto hay que hacerlo así porque no es posible determinar el número de dispositivo que Xen asigna a cada dispositivo insertado.

De hecho sí es posible, ya que el número asignado es una combinación de los números mayor y menor de dispositivo, pero aparte de complicado sólo vale cuando la máquina virtual es Linux.


Debido a ello debemos determinar el número de dispositivo asignado, para lo que hallamos la diferencia entre las dos listas de dispositivos (diff -u /var/xenhotplug/$1/{pre,post}list > /var/xenhotplug/$1/difflist), que es la línea correspondiente al dispositivo añadido, y extraemos de ella el citado número (cat /var/xenhotplug/$1/difflist |grep ^+|tail -n -1|awk '{print \$1}'|awk -F + '{print \$2}' > /var/xenhotplug/$1/vdev) y lo registramos también. Trabajo hecho.

Por otro lado, el segundo bloque, que se ejecuta cuando se retira un dispositivo, parece un poco más sencillo. Revisa a través de todos los dispositivos registrados en busca del que acaba de desaparecer (for file in /var/xenhotplug/* ; do ... done), poniendo temporalmente el nombre de cada uno en la variable $filecont (filecont=`cat $file/devname`) y comparándolo con el que buscamos (if [ "$filecont" = "$2" ] ; then ... fi). Cuando lo encuentra, registra temporalmente en la variable $vdev el número de dispositivo buscado (vdev=`cat $file/vdev`), llama a otro programa que se asegura de desconectar ese dispositivo de la máquina virtual (/usr/local/sbin/assure-detach.sh $virt $vdev 2>&1) y desregistra el dispositivo (rm -rf $file).

Directorio de registro



Bueno, el funcionamiento del directorio de registro quedó ya explicado arriba, con el fichero que lo usa, pero en resumen: el directorio /var/xenhotplug se llena con otros directorios, uno por cada dispositivo registrado. En el interior de estos se generan varios ficheros, de los cuales sólo son importantes dos, /var/xenhotplug/X/vdev, que contiene el número de dispositivo virtual Xen, y /var/xenhotplug/X/devname, que contiene el nombre de dispositivo del hierro que se utilizó para añadir a la máquina virtual. Crear uno de estos directorios con esos dos ficheros es lo que llamamos registrar un dispositivo, y borrarlo es lo que llamamos desregistrarlo.

assure-detach.sh



Estoooo... no lo voy a presentar. No está acabado. Y de hecho no funciona bien (aún). Pero básicamente lo que hace es llamar a /usr/sbin/xm block-detach $virt $vdev. El problema que tiene en este momento es que si el dispositivo está en uso en la máquina virtual, ésta ignora la orden de eliminarlo. Estoy trabajando en ello.

Firmar discos duros

PUBLICADA, PERO PENDIENTE DE MEJORAR EL FORMATO (no habrá cambios de contenido, salvo correcciones)


¿Cómo distinguir entre sí dos discos duros?

Evidentemente, la pregunta no es cómo distinguirlos por fuera, para eso basta con una pegatina. Sino cómo distinguirlos desde el ordenador.

La respuesta típica sería usar el número de serie. Una aplicación sencilla como smartctl nos da tan interesante dato, que además es único para cada disco.

Vale. Pero es que tengo el disco en una de esas cajas USB tan monas, que me dejan usar como disco externo USB un disco IDE.

Pues la respuesta general sería que lo llevo chungo. No obstante, aquí viene la solución. La he desarrollado cuando me surgió la necesidad de distinguir entre sí discos externos que utilizamos en Grupo CPD (la empresa para la que trabajo) para hacer copias de seguridad con Espiral (un programa que no uso) o con Dirvish (que yo sí uso, es la base de Espiral, y está basado en el viejo y eficaz rsync).

Un disco particionable almacena su tabla de particiones en el sector de arranque, ese lugar tan conocido llamado a veces MBR, objetivo de virus y elementos indeseables semejantes.

El sector de arranque de un disco particionable cualquiera, independientemente de que se utilice en Windows o Linux (no me comprometo con otros sistemas), es el primer sector del disco duro, es decir, los primeros 512 bytes, y tiene una estructura muy definida.

Los primeros 440 bytes (del 0 al 439) contienen el cargador de arranque del sistema. Es un programa muy pequeño que se carga en memoria al arrancar el ordenador, y se encarga de arrancar el ordenador desde una de las particiones disponibles. Ejemplos de programas de este tipo son el MBR de MS-DOS, LILO y la primera etapa (stage 1) de GRUB.

Los 64 bytes del 446 a 509 contienen la tabla de particiones. Y los dos últimos (510 y 511) contienen una firma especial, los números 55h y AAh, que identifican el sector completo como un sector de arranque.

Y ¿qué pasa con los bytes 440 al 445 de los que no he hablado?

Algunos sistemas, como Windows NT y sus sucesores, los emplean como un modo de identificar cada disco. El fdisk de Linux, además, no los toca. No me comprometo con otros programas como LILO, GRUB o el fdisk de MS-DOS/Windows.

Así, los 4 bytes del 440 al 443 son llamados "Número de serie de disco de Windows NT", y los otros dos, 444 y 445, no se emplean.

Personalmente empleo esos 4 bytes para firmar los discos IDE en cajas USB en algunos de mis sistemas. El primer disco contiene los bytes EE EE EE 01 (hexadecimal, en orden de disco, no olvidemos que la arquitectura Intel x86 almacena las palabras de 2 bytes con el byte de menor peso delante). El segundo disco contiene los bytes EE EE EE 02, y así sucesivamente. Aún no he llegado a tener 255 discos externos...

¿Y cómo lo hago? Fácil de lo fácil. Me descargo el sector de arranque del disco que quiero tocar, por ejemplo con dd:
dd if=/dev/sdb of=sdb_mbr count=1 bs=512

lo edito con un editor hexadecimal:
khexedit sdb_mbr

y lo vuelvo a meter en el disco:
dd if=sdb_mbr of=/dev/sdb

Y ya tengo mi disco firmado.

Para beneficio de los usuarios del editor hexadecimal, y ya que las direcciones suelen venir en hexadecimal, las repito en ese formato:






Código0 a 4390000h a 01B7h
Número de serie de disco de Windows NT440 a 44301B8h a 01BBh
Sin uso444 a 44501BCh a 01BDh
Tabla de particiones446 a 50901BEh a 01FDh
Firma mágica AA55h510 a 51101FEh a 01FFh

lunes, 13 de agosto de 2007

Certificados X509 para Apache 2 (I): Clave de la Autoridad Certificadora

PUBLICADA, PERO PENDIENTE DE MEJORAR EL FORMATO (no habrá cambios de contenido, salvo correcciones)



Bueno, a ver cómo van las cosas. Llegas un día y decides que quieres cambiar de protocolo: tu servidor Apache, con la costumbre de escuchar por el puerto 80 (HTTP), quieres que se ponga a escuchar en el puerto 443 (SSL) para usar eso que llaman el "protocolo HTTPS".

En una relación cifrada cliente-servidor como esa normalmente hay tres partes implicadas, no dos. Está el cliente, que no tiene motivos para confiar en el servidor, está el servidor, que no tiene motivos para confiar en el cliente, y está la Autoridad Certificadora. Así, con mayúsculas. Y la idea es que tanto el cliente como el servidor confían en la Autoridad Certificadora. Así, si el servidor presenta al cliente un documento de identidad certificado por la Autoridad Certificadora, el cliente puede estar seguro de que el servidor es quien realmente dice ser, y no un impostor.

Por eso, el papel de la Autoridad Certificadora es tan importante. Si he de meter mi número de tarjeta de crédito para hacer un pago en una página, quiero estar seguro de que el servidor que lo va a recibir es quien dice ser, y estoy seguro si mi navegador me dice que lo es.

Mi navegador me lo dice si el servidor se identifica mediante un certificado firmado por una Autoridad Certificadora en la que confíe. Y los navegadores confían en unas pocas Autoridades Certificadoras que tienen un negocio enorme con la venta de certificados. Evidentemente, los navegadores no confiarán en los certificados de cualquier autoridad certificadora, ya que cualquier ladrón informatizado que falsee una página puede falsear también su certificado creando una autoridad certificadora falsa.

Así que si no quieres pagar los 1500 (sí, mil quinientos) dólares que Verisign[1] u otros piratas semejantes cobran por un certificado, o si simplemente tienes algún motivo para convertirte en tu propia CA (Certificating Authority - Autoridad Certificadora), puedes hacerlo sin demasiados problemas (igual que lo haría el falsificador). Ten en cuenta que un servidor con SSL no funcionará sin un certificado.

Sólo necesitas tener instalado el paquete openssl, que proporciona las piezas y programas necesarios.

Crear la clave maestra de la CA


Lo primero es pensar en dónde almacenaremos todos los ficheros sensibles que vamos a crear. Lo ideal sería que creáramos un directorio sólo para esas cosas, con permisos adecuados, y que nos metiéramos en él antes de empezar a trabajar.

EDITO: He mejorado estas órdenes en una nueva entrada.

Nota: los comandos introducidos a partir de ahora son válidos en un sistema operativo de tipo UNIX, en particular yo los he probado en Linux, en la distribución Debian.

Antes de crear la clave crearemos un directorio llamado private para almacenar en él la clave privada:
mkdir private

A continuación creamos la clave:
openssl genrsa -des3 -out private/cakey.pem 2048

El comando openssl es la base de todo nuestro trabajo. Tiene un buen número de subcomandos para diferentes tareas. En esta ocasión utilizamos el subcomando genrsa para, precisamente, generar una clave RSA. Las opciones indican, por ese orden, que la clave generada estará protegida mediante un cifrado 3DES con contraseña (-3des) y que se almacenará en el directorio private en el fichero cakey.pem (-out private/cakey.pem), mientras que el argumento 2048 indica que la clave generada tendrá 2048 bits de longitud. La clave se almacenará en formato PEM, por eso ponemos la extensión como .pem, mientras que el nombre cakey indica, literalmente, que se trata de la clave de la CA. Esa es, además, la razón (aparte de que lo recomienda la documentación) de que la guardemos en el directorio private.

En la documentación oficial de OpenSSL se detalla que el subcomando genrsa es obsoleto y se debería usar en su lugar el subcomando genpkey, en cuyo caso la línea de órdenes sería:
openssl genpkey -des3 -outform PEM -out private/cakey.pem -pkeyopt rsa_keygen_bits:2048

Ojo: No he probado esta línea.

Bueno, en cualquier caso, como hemos pedido que la clave quede protegida mediante contraseña, tendremos que introducir la contraseña elegida, y volverla a introducir para confirmar que la hemos escrito bien:
openssl genrsa -des3 -out private/cakey.pem 2048
Generating RSA private key, 2048 bit long modulus
..........................................+++
.............................................+++
e is 65537 (0x10001)
Enter pass phrase for private/cakey.pem:
Verifying - Enter pass phrase for private/cakey.pem:

Como se puede ver, el generador nos indica que está creando una clave privada RSA de 2048 bits, con el exponente 65537, lo que es un dato técnico que no nos importa ahora.

¿Y qué son todos esos puntitos y cruces? Pues muy sencillo: hay que encontrar dos números primos, y se prueban números al azar a ver si lo son. Cada puntito es un número probado que pasa una prueba de primalidad sencilla, y cada cruz es un número que pasa una prueba mayor. El salto de línea es un número que pasa todas las pruebas, es decir, que es uno de los dos números primos que estamos buscando.

Ojo: no pierdas nunca la contraseña que has introducido para cifrar la clave privada de la CA. Sin esa contraseña, toda la estructura de la CA queda inutilizada.

Seguiré en otro artículo con la creación del certificado raíz, la creación de la clave del servidor y la creación del certificado del servidor.

Comienza la desbitácora

Bueno, aquí estamos.

He creado esta desbitácora (bitácora de desarrollo, igual que devlog de developer log) para ir apuntando todo lo que vaya descubriendo sobre la informática y el desarrollo de aplicaciones informáticas... y matemáticas.

No busquen aquí pensamientos ni cosas parecidas. Para eso está la bitácora madre: Envite.