Creación y Verificación de un Certificado Covid.
En primer lugar, se requiere ordenar los datos relativos al certificado (nombre, edad, fecha vacunación, etc.) en un formato entendible para los sistemas informáticos.
El formato JSON es un formato similar al XML, pero por lo general es más corto y más fácil de analizar.
XML es un lenguaje de marcado similar a HTM, pero menos cómodo por la cantidad y longitud de datos.
La estructura común se describe a continuación:
«JSON»: {
«ver»: <version information>,
«nam»: {
<person name information>
},
«dob»: <date of birth>,
«v» or «t» or «r»: [
{<vaccination dose or test or recovery information, one entry>}
]
}
Una vez definida la estructura se genera el archivo JSON con los daos del portador del certificado:
{«6″:1639725847,»4″:1684671300,»1″:»ES»,»-260″:{«1»:{«ver»:»1.3.0″,»nam»:{«fn»:»CORDON VERA»,»gn»:»FRANCISCO AN»,»fnt»:»CORDON<VERA»,»gnt»:»FRANCISCO<AN»},»dob»:»19xx-01-14″,»v»:[{«tg»:»840539006″,»vp»:»1119349007″,»mp»:»EU/1/20/1507″,»ma»:»ORG-100031184″,»dn»:2,»sd»:2,»dt»:»2021-12-15″,»co»:»ES»,»is»:»Servicio Andaluz de Salud»,»ci»:»01ES01V5201A44C5F0A11ECXXXX#3″}]}}}
Estos serian mis datos con algunas alteraciones por seguridad.
El paso siguiente seria convertirlo a CBOR.
CBOR es una alternativa a JSON para serializar datos con menos bytes y con menos esfuerzo informático. Menos bytes significa más velocidad de solicitud y respuesta y menos uso de memoria. Para datos pequeños, no es visible, pero si guardamos algunos bytes para millones de solicitudes por segundo, lo hará.
Conversión de mi certificado en CBOR:
Para ello he utilizado un algoritmo en Python, de mi propia creación (existen multitudes en la nube).
¤a6a¼;a4dj
Da1bESd-260¡a1¤cvere1.3.0cnam¤bfnkCORDON VERAbgnlFRANCISCO ANcfntkCORDON<VERAcgntlFRANCISCO<ANcdobj1967-01-14avªbtgi840539006bvpj1119349007bmplEU/1/20/1507bmamORG-100031184bdn[1]bsd[1]bdtj2021-12-15bcobESbisxServicio Andaluz de Saludbcix‑01ES01V520XX44C5F0A11EC99400#3
Ahora la empresa certificadora del documento, en mi caso el Servicio Andaluz de salud, firma este archivo con su clave privada y secreta, cifrando el certificado.
Posteriormente y para reducir los datos lo comprimimos con el formato JLIB (muy similar al conocido ZIP)
El último paso sería volver a codificar los datos, en esta ocasión en Base-45, un estándar de codificación propuesto para codificar datos con 45 caracteres, por ser la codificación compacta más adecuada para códigos QR.
El resultado sería:
HC1:NCFOXN%TSMAHN-HL S*F19I0V-G -D-AH-UC1ROT$SD P++IAEFF%GDJ5BYEOTAF/8X*G3M9JUPY0BZW4V/AY73CNN1M3I6H3H31H3CB75J7BG7LOJW77/AJM83NJJIC5SP499TP+MX/KS96/-KKTCY73JC3MD34LT.LJ3ZC58J0ATC%CB+2W/SS KKJ5XP40P0X5TFV4R/S09T./0LWTKD3323/I0SRJB/S7-SN2H N37J3JFTULJ5CB8X2.36D-I/2DBAJDAJCNB-43 X4VV2 73-E3GG3V20-7TZD5CC9T0HB/CCNNT*8JZII7JSTNB95726KZ5QNQ8ORQFA92SD9R%E53X7$ 74/QT+Q5.OCA7T5MN%4IK04T9P8QHIG02J$%25I3KC3X83F67*WUY6KU2VOAF:MDX-GSBWPJNFVVINFEXGT5S-H5ZWP4 4M 18GT:HE9JJS1IXWJP8R25AZT74ZS5BQQY6N-6F5N/9JDM3A5H4SNYM0T2L. E
Y con esto tendríamos el QR del certificado terminado.
Certificados fraudulentos
Últimamente están saliendo noticias de falsificaciones de estos certificados, la única forma de realizar uno que engañe a la aplicación que los verifica, seria conociendo la firma o clave privada de alguna de las entidades certificadoras. Por desgracia ya se han filtrado algunas en internet:
kid: 53FxxjX/4aJs=
key: <EllipticCurvePublicNumbers(curve=secp256r1, x=592244247113166610848779733018418xx584140021680113528472675651838972371380627, y=54841068689176540860306147861xx76004028606373898471432794562118907413910993957>
kid: HhkeqxxrtQ0U=
key: <EllipticCurvePublicNumbers(curve=secp256r1, x=58474994431552591028397454866551xx597403245555156280299836700796569353760692, y=9416038953242826359976521318443154xx48511972729790083405292977908540518398962>
He modificado algunos caracteres por razones obvias.
VR 0: C=FR,ID=URN:UVCI:01:FR:W7V2BE46QSBJ#L,ISS=CNAM
KID: 53FOjX/4aJs=
Issued At: 2021-10-27 11:20:48 UTC
Signed By: CN=DSC_FR_023,OU=180035024,O=CNAM,C=FR (issued by: CN=CSCA-FRANCE,O=Gouv,C=FR)
Expiration: 2023-10-13 22:00:00 UTC
Personal Name: MICKEY MOUSE
DOB: 2001-12-31
Estas claves privadas ya han sido revocadas y no es posible falsificar certificados con ellas, siempre y cuando esten actualizadas las App de verificación.
Validación del certificado
El proceso de descodificación del certificado QR se obtiene realizando todos los pasos en el sentido inverso, pero con la salvedad que el descifrado se realiza con la clave pública la cual es conocida y facilitada por la entidad certificadora.
La aplicación para comprobar si es correcto y cumple los requisitos no necesita conexión a internet, aunque si es conveniente actualizaciones diarias para obtener los certificados revocados, añadir nuevas claves publicas de nuevos organismos y conocer los criterios de valoración para dar el color verde (tener la pauta completa, tiempo pasado desde la última vacuna, etc.) ya que varían según el país emisor.
En este ejemplo mediante una App de mi creación, puedo validar fácilmente los certificados:
Parte de código utilizado en mi APP:
Sub GreenPassParse( s As String ) As String
Dim partialparse As StringBuilder
partialparse.Initialize
json.Initialize( s )
Dim walker As Map = json.NextObject
Dim country As String = walker.Get(«1»)
Dim rec260 As Map = walker.Get(«-260»)
Dim rec1 As Map = rec260.Get(«1»)
Dim name As Map = rec1.Get(«nam»)
partialparse.Append( «Certificado emitido en: » & country & » Apellidos y nombre: » & name.Get(«fn») & «, » & name.Get(«gn») & » Fecha de nacimiento: » & rec1.Get(«dob»)).Append(CRLF)
Dim vacclist As List
vacclist.Initialize
Dim testlist As List
testlist.Initialize
Dim recoverylist As List
recoverylist.Initialize
Try
vacclist = rec1.Get(«v») ‘ vaccination
If vacclist.IsInitialized Then
For Each rec As Map In vacclist
Dim a As Int = rec.Get(«tg»)
Dim against As String
If a = 840539006 Then
against = «Covid»
Else
against = «unknown target»
End If
Dim d As Int = rec.Get(«dn»)
partialparse.Append( «Fecha de vacunación: » & rec.Get(«dt») & » Emisor del certificado: » & rec.Get(«is») & » Número de dosis: » & d & » Enfermedad que se previene: » & against& » Identificador del certificado: » & rec.Get(«ci»)).Append(CRLF)
Next
End If
Catch
Log(LastException)
End Try
Try
testlist = rec1.Get(«t»)
If testlist.IsInitialized Then
For Each rec As Map In testlist
Dim a As Int = rec.Get(«tg»)
Dim against As String
If a = 840539006 Then
against = «Covid»
Else
against = «unknown target»
End If
Dim r As Int = rec.Get(«tr»)
Dim testresult As String
If r = 260415000 Then
testresult = «undetected»
Else if r = 260373001 Then
testresult = «detected…»
Else
testresult = «unknown»
End If
partialparse.Append( «date of test: » & rec.Get(«sc») & » where: » & rec.Get(«co») & » against: » & against & » result: » & testresult).Append(CRLF)
Next
End If
Catch
Log(LastException)
End Try
Try
recoverylist = rec1.Get(«r»)
If recoverylist.IsInitialized Then
For Each rec As Map In recoverylist
Dim a As Int = rec.Get(«tg»)
Dim rfrom As String
If a = 840539006 Then
rfrom = «Covid»
Else
rfrom = «unknown target»
End If
partialparse.Append( «recovery from: » & rfrom & » valid until: » & rec.Get(«du»)).Append(CRLF)
Next
End If
Catch
Log(LastException)
End Try
Return(partialparse.ToString )
End Sub
Sub SanipassParse( s As String ) As String
Dim matcher As Matcher
matcher = Regex.Matcher(«DC\d\d», s)
If matcher.Find Then
Dim pattern As String = matcher.Group(0)
End If
Dim startpos As Int = s.IndexOf( pattern )
If startpos > 0 Then
s = s.SubString( startpos )
End If
‘ DC01 DC02 DC03 DC04 all exist and with different
‘ formats
Dim masterregex As String
Dim personaldata As Int
If pattern.CompareTo(«DC04») = 0 Then
masterregex = «(.{2,2})(.{2,2})(.{4,4})(.{4,4})(.{4,4})(.{4,4})(.{2,2})(.{2,2})(.{2,2})(.*)»
‘ find all the various «groups» in the string
personaldata = 10
else If pattern.CompareTo(«DC03») = 0 Then
masterregex = «(.{2,2})(.{2,2})(.{4,4})(.{4,4})(.{4,4})(.{4,4})(.{2,2})(.{2,2})(.*)»
‘ group(7) type of cert
personaldata = 9
else If pattern.CompareTo(«DC02») = 0 Or pattern.CompareTo(«DC01») Then
masterregex = «(.{2,2})(.{2,2})(.{4,4})(.{4,4})(.{4,4})(.{4,4})(.{2,2})(.*)»
‘ group(7) type of cert
personaldata = 8
Else
Log(«unknown version: » & pattern)
End If
If masterregex.Length > 0 Then ‘ we have a matching pattern
matcher = Regex.Matcher( masterregex, s )
If matcher.find Then
Dim certtype As String = matcher.Group(7)
If certtype = «B2» Then
certtype = «test»
else if certtype = «L1» Then
certtype = «vaccination»
Else
certtype = «unknown type of certificate»
End If
Dim importantstuff = matcher.Group( personaldata ) ‘ will vary depending on version
Dim personregex = «F0(.*?)%1DF1(.*?)%1DF2(.{8,8})F3(.)F4(.*?)%1DF5(.)F6(.*?)%1F» ‘ ends with %1F the rest is signature
Dim personmatcher As Matcher
personmatcher = Regex.Matcher(personregex, importantstuff)
If personmatcher.Find Then
Dim dob As String = personmatcher.Group(3)
dob = dob.SubString2(0,2) & «-» & dob.SubString2(2,4) & «-» & dob.SubString2(4,8)
Log(certtype & » for: » & personmatcher.Group(1) & «, » & personmatcher.Group(2) & » » & dob & » » & personmatcher.group(4) & » «)
End If
End If
End If
End Sub
Se verifican tres aspectos:
- Que el certificado esta firmado electrónicamente por uno de los emisores permitidos.
- Que el certificado no esta revocado.
- Que el certificado cumple con los criterios establecidos de validez en España.
Si alguno de estos tres aspectos no se cumplen, el certificado se considera inválido.
– Puede funcionar sin necesidad de acceso a Internet.
– No almacena ninguna información del certificado verificado, ni realiza ninguna comunicación al respecto.