Firmar XML 100% VFP sin DLL,

5280 views
Skip to first unread message

Fernando Mora

unread,
Jan 8, 2019, 10:16:52 AM1/8/19
to Comunidad de Visual Foxpro en Español
Sin usar DLL externas, solo usando el proveedor criptografico original de Windows CryptoApi CSP y CNG, desde el almacén de certificados de Windows, es recomendable configurar sus librerías para hacerlo desde ahí, para que funcione tanto con firmas en archivo o token usb.podemos fácilmente decodificar el certificado, extraer el serial, Issuer, modulus, exponente, etc. Exportar el par de claves (pública y privada si va a usar CNG) para poder crear el o los hash y generar el firmado del mismo.  Usando solo CSP puede firmar en MD2, MD5, Sha1, pero con CNG se pude firmar en formato Sha1, Sha256, Sha512, etc. Ahora bien, exportando el par de claves supongo que es posible (si de desea), usar otro proveedor criptografico (Openssl, Nss, etc) pero aun no lo he intentado. Esto ha funcionado excelente usando VFP9 con Windows 7 SP2, voy a probar en diferentes versiones de Windows, desde XP hasta 10 a ver como nos va, Si alguien esta interesando en este tema y desea orientación deje su correo para pasarle documentación. 

vfp_cng_csp.png




Fernando Mora

unread,
Jan 8, 2019, 10:20:26 AM1/8/19
to Comunidad de Visual Foxpro en Español
Por cierto, estos son las declaraciones Api para exportar el par de claves: 

DECLARE LONG CryptAcquireCertificatePrivateKey IN crypt32;
LONG pCert,;
LONG dwFlags,;
LONG pvParameters,;
LONG @phCryptProvOrNCryptKey,;
LONG @pdwKeySpec,;
LONG @pfCallerFreeProvOrNCryptKey

DECLARE LONG CryptGetUserKey IN Advapi32;
LONG hProv,;
LONG dwKeySpec,;
LONG @phUserKey

DECLARE LONG CryptExportKey IN Advapi32;
LONG hKey,;
LONG hExpKey,;
LONG dwBlobType,;
LONG dwFlags,;
STRING @pbData,;
LONG @pdwDataLen


Hernan Serrano

unread,
Jan 8, 2019, 10:40:38 AM1/8/19
to publice...@googlegroups.com
Me interesa el tema. Podrias pasarme la documentacion.
--
Tico Support S. A.
Tel. (506)8819-4369

Kyo2104

unread,
Jan 8, 2019, 11:13:33 AM1/8/19
to Comunidad de Visual Foxpro en Español
Estimado Fernando, Buen Dia

Has despertado mi interes, en esta posible solucion con Foxpro, asi se evitaria como tu mismo indicas el empleo de DLL externas, te agradeceria mucho me hagas llegar la informacion respectiva.

Jean Pierre Adonis De La Cruz Garcia

unread,
Jan 8, 2019, 11:51:15 AM1/8/19
to Comunidad de Visual Foxpro en Español
Ojala que no resulte un Sabio por ahi que desde VFP cree una DLL para firmar XML jajajaja ya seria el colmo.

Rene Jara

unread,
Jan 8, 2019, 1:32:35 PM1/8/19
to publicesvfoxpro
Fernando
Te felicito por tu iniciativa que es un tema muy recurrente en estos tiempo
estoy muy interesado en ese desarrollo


  



Cordialmente
Rene Jara Muñoz
         

la información contenida en esta transmisión es sólo para el uso personal y confidencial de la persona o entidad a la que va dirigida. Si el lector de este mensaje no es el destinatario o un agente responsable de entregar al destinatario, se le notifica que cualquier revisión, difusión, distribución o copia de este mensaje está estrictamente prohibida. Si usted ha recibido este mensaje por error, por favor notifique al remitente inmediatamente. Gracias.



William López

unread,
Jan 8, 2019, 2:20:37 PM1/8/19
to Comunidad de Visual Foxpro en Español
Gracias Fernando. En nuestro país estamos por comenzar este año a trabajar con la Facturación Electrónica, y si me interesa comenzar a introducirme en ello. Por favor me puedes incluir.
Gracias.

Antonio.xt

unread,
Jan 8, 2019, 3:15:55 PM1/8/19
to Comunidad de Visual Foxpro en Español

Bien Fernando, muy bueno tu plan.
Igualmente me interesa a mi tambien, podrias incluirme...

 

Fernando Mora

unread,
Jan 8, 2019, 8:33:15 PM1/8/19
to Comunidad de Visual Foxpro en Español
Saludos amigos, Agrego algo a la info que ya les envié a sus correos. La región CERT_INFO tiene una longitud de 112 bytes, ese dato no esta por ningún lado, lo fui descubriendo explorando esa región de 4 en 4 bytes. avance hasta 200 pero solo encontré datos hasta el 112. pueden hacer lo mismo, y confirmen como les fue, por lo menos los certificados que entregan las entidades certificadas acá en Ecuador son así. Otra cosa, dominando el tema del manejo de certificados digitales, usando simple api se puede crear certificados digitales auto-firmados en VFP.... no lo he hecho pero veo que existe unas funciones para eso, sería de estudiarlo... uffff sería genial hacer eso desde VFP sin necesidad de DLL hechas en csharp u otros. 

Fernando Mora

unread,
Jan 8, 2019, 9:01:16 PM1/8/19
to Comunidad de Visual Foxpro en Español
DECLARE LONG CertOpenSystemStore IN Crypt32;
LONG hprov,;
STRING szSubsystemProtocol

DECLARE LONG CryptUIDlgSelectCertificateFromStore IN Cryptui;
LONG hCertStore,;
LONG hWnd, ;
STRING @pwszTitle, ;
STRING @pwszDisplayString, ;
LONG dwDontUseColumn, ;
LONG dwFlags,;
STRING pvReserved

hStore = CertOpenSystemStore(0, "MY")

pCertContext=0
pCertContext=CryptUIDlgSelectCertificateFromStore(hStore , 0, null, null, 1, 0, null)
IF pCertContext=0
RETURN .F.
ENDIF

*---- Decodificación de certificado
SET STEP ON
lcCERT_CONTEXT = SYS(2600, pCertContext, 20)
ndwCertEncoTyp = SUBSTR(lcCERT_CONTEXT, 1, 4)
pbCertEncoded = SUBSTR(lcCERT_CONTEXT, 5, 4)
cbCertEncoded = SUBSTR(lcCERT_CONTEXT, 9, 4)
lcCERT_INFO = SUBSTR(lcCERT_CONTEXT, 13, 4)
hCertStore = SUBSTR(lcCERT_CONTEXT, 17, 4)

*---- Declaración de Apis para decodificación
DECLARE LONG CryptBinaryToString IN Crypt32;
STRING pbBinary, ;
LONG cbBinary, ;
LONG dwFlags,;
STRING @pszString, ;
LONG @pcchString

DECLARE LONG CryptDecodeObject IN crypt32;
LONG dwCertEncodingType,;
STRING lpszStructType,;
STRING pbEncoded,;
LONG cbEncoded,;
LONG dwFlags,;
STRING @pvStructInfo,;
LONG @pcbStructInfo



César Ruiz

unread,
Jan 8, 2019, 9:15:44 PM1/8/19
to publice...@googlegroups.com
El tema me interesa montones si puedes enviarme la documentación te lo agradeceria mucho, excelente iniciativa.

Gracias

mpulla

unread,
Jan 9, 2019, 8:15:57 AM1/9/19
to Comunidad de Visual Foxpro en Español
Hola Fernando
Me interesa la información, por favor envíame la documentación

Saludos.
Mauricio


El martes, 8 de enero de 2019, 10:16:52 (UTC-5), Fernando Mora escribió:

ARVIOS

unread,
Jan 9, 2019, 9:59:38 AM1/9/19
to Comunidad de Visual Foxpro en Español
Fernando muchas gracias por tu aporte. Me gustaria mucho que por favor me envíes informacion del tema.

Arnoldo Villamizar


El martes, 8 de enero de 2019, 10:16:52 (UTC-5), Fernando Mora escribió:

Alejandro Garcia Garay

unread,
Jan 9, 2019, 11:20:09 AM1/9/19
to Comunidad de Visual Foxpro en Español
Gracias por el dato, ¿cómo se puede obtener información para hacerlo?.

Saludos. 


Alejandro. 

Jose Antonio Blasco

unread,
Jan 9, 2019, 12:08:49 PM1/9/19
to Comunidad de Visual Foxpro en Español
Fernando, yo tambien estoy interesado.

Gracias.

Julio Cesar Medrano Melgar

unread,
Jan 9, 2019, 9:06:00 PM1/9/19
to publice...@googlegroups.com
Que tal Fernando:

Interesadisimo en el tema, por favor un poco mas de información

Saludos
 
Lic. Julio Medrano
Consultor Inform??tico

"Un buen programador nunca muere solo se pierde en un proceso"




De: 'mpulla' via Comunidad de Visual Foxpro en Español <publice...@googlegroups.com>
Enviado: miércoles, 9 de enero de 2019 13:15
Para: Comunidad de Visual Foxpro en Español
Asunto: [vfp] Re: Firmar XML 100% VFP sin DLL,
 

Miguel A.

unread,
Jan 10, 2019, 1:48:01 PM1/10/19
to Comunidad de Visual Foxpro en Español
Fernando, yo tambien estoy interesado.

Gracias.
Miguel A.

Kleinehassler

unread,
Jan 10, 2019, 6:15:20 PM1/10/19
to Comunidad de Visual Foxpro en Español
Fernando,

Enviar información al correo
di...@hassler.ec

Gracias

Jc Mejia

unread,
Jan 10, 2019, 11:10:58 PM1/10/19
to publice...@googlegroups.com
Hola Fernando,

Yo también estoy interesado!

Gracias,

JC Mejía

El mar., 8 ene. 2019 9:16 a. m., Fernando Mora <servipcco...@gmail.com> escribió:

JimmyFox

unread,
Jan 10, 2019, 11:14:23 PM1/10/19
to publice...@googlegroups.com
Hola Fernando,

Me interesa el tema, podrías pasarme la documentación a jimmyf...@gmail.com

Saludos.

Fernando Mora

unread,
Jan 11, 2019, 12:13:13 AM1/11/19
to Comunidad de Visual Foxpro en Español
Saludos compas, estoy armando un proyecto open para este tema, el problema que es largo, hay que hacer varias cosas, para hacer algo completo espero que alguien me ayude con esto, ya que ando con el tiempo medido, Por ahora armé algo básico de el firmado, espero que este código ayude a varios colegas a encontrar el camino correcto para que armen sus proyectos criptograficos para sus países. De VFP tienen que manejar Sys(2600) para manejar punteros de datos, revisen la ayuda de vfp sobre esa función es clave para esto, al igual que CTOBIN() con esas dos funciones tienen para defenderse bastante en este tema. Lo otro es comprender las Api de Crypto Api tanto de CSP como de CNG, Esta dirección de MS es vital para el tema https://docs.microsoft.com/en-us/windows/desktop/seccrypto/decoding-a-cert-info-structure, revisen los conceptos y uso de las funciones, Pasar esos ejemplos de C++ a Vfp no es tan difícil. Pero si hay que leer harto. Aquí les dejo una capture de como luce un par de claves publico/privada de un certificado, (formato garabato :) )

VFP_EXPORT_PAR_KEY.png


Fernando Mora

unread,
Jan 11, 2019, 12:26:31 AM1/11/19
to Comunidad de Visual Foxpro en Español
Aquí esta un ejemplo bastante completo, adicional a esto aprendan a canonalizar datos para realizar correctamente los digest-values y el firmado de los mismo.  Aquí hay un portal que es de gran ayuda, mejor explicado que ese portal imposible. Revisen la parte 1 y 2 de esta web. https://www.di-mgt.com.au/xmldsig2.html
 
Comenzamos el 2019 con pie derecho.... agradecer no cuesta nada. saludos a todos y bendiciones.


DECLARE LONG CertOpenStore IN crypt32;
LONG lpszStoreProvider,;
LONG dwEncodingType,;
LONG hCryptProv,;
LONG dwFlags,;
STRING pvPara
DECLARE LONG CertOpenSystemStore IN crypt32;
LONG hprov,;
STRING szSubsystemProtocol

DECLARE LONG CertCloseStore IN crypt32;
LONG hCertStore,;
LONG dwFlags

DECLARE LONG CryptUIDlgSelectCertificateFromStore IN Cryptui;
LONG hCertStore,;
LONG hWnd,;
STRING @pwszTitle,;
STRING @pwszDisplayString,;
LONG dwDontUseColumn,;
LONG dwFlags,;
STRING pvReserved

DECLARE LONG CryptAcquireCertificatePrivateKey IN crypt32;
LONG pCert,;
LONG dwFlags,;
LONG pvParameters,;
LONG @phCryptProvOrNCryptKey,;
LONG @pdwKeySpec,;
LONG @pfCallerFreeProvOrNCryptKey

DECLARE LONG CryptGetUserKey IN Advapi32;
LONG hProv,;
LONG dwKeySpec,;
LONG @phUserKey

DECLARE LONG CryptExportKey IN Advapi32;
LONG hKey,;
LONG hExpKey,;
LONG dwBlobType,;
LONG dwFlags,;
STRING @pbData,;
LONG @pdwDataLen
*------------------------- CNG 
DECLARE LONG BCryptOpenAlgorithmProvider IN BCrypt; 
LONG @phAlgorithm,;
STRING pszAlgId,; 
STRING pszImplementation,;
LONG dwFlags

DECLARE LONG BCryptImportKeyPair IN BCrypt; 
LONG hAlgorithm,;
LONG hImportKey,;
STRING pszBlobType,;
LONG @phKey,;
STRING pbInput,;
LONG cbInput,; 
LONG dwFlags 

DECLARE LONG BCryptSignHash IN BCrypt;  
LONG hKey,;
LONG @pPaddingInfo,;
STRING pbInput,;  
LONG cbInput,;
STRING @pbOutput,;
LONG cbOutput,;  
LONG @pcbResult,;
LONG dwFlags

DECLARE LONG BCryptGetProperty IN BCrypt;
LONG hObject,;
STRING pszProperty,;
LONG @pbOutput,;
LONG cbOutput,;
LONG @pcbResult,;
LONG dwFlags

DECLARE LONG BCryptCreateHash IN BCrypt; 
LONG hAlgorithm,;
LONG @phHash,;
STRING @pbHashObject,;
LONG cbHashObject,;
STRING pbSecret,;
LONG cbSecret,; 
LONG dwFlags

DECLARE LONG BCryptHashData IN BCrypt; 
LONG hHash,;
STRING pbInput,;
LONG cbInput,;
LONG dwFlags 

DECLARE LONG BCryptFinishHash IN BCrypt; 
LONG hHash,;
STRING @pbOutput,;
LONG cbOutput,;
LONG dwFlags 

DECLARE LONG BCryptDestroyHash IN BCrypt; 
LONG hHash 

DECLARE LONG BCryptDestroyKey IN BCrypt; 
LONG hKey

DECLARE LONG BCryptCloseAlgorithmProvider IN BCrypt; 
LONG hAlgorithm,;
LONG dwFlags

*------------------------ Kernel
DECLARE INTEGER GetLastError IN Kernel32

DECLARE LONG FormatMessage IN Kernel32;
  LONG dwFlags,;
  STRING @lpSource,;
  LONG dwMessageId,;
  LONG dwLanguageId,;
  STRING @lpBuffer,;
  LONG nSize,;
  LONG Arguments

DECLARE LONG GetProcessHeap IN Kernel32

DECLARE LONG HeapAlloc IN Kernel32;
LONG hHeap,;
LONG dwFlags,;
LONG dwBytes

DECLARE LONG HeapFree IN Kernel32;
LONG hHeap,;
LONG dwFlags,;
LONG lpMem
*------------------------ CONSTANTES 
#DEFINE CERT_STORE_PROV_SYSTEM 10 
#DEFINE CERT_SYSTEM_STORE_CURRENT_USER 0x00010000
#DEFINE CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG 0x00010000
#DEFINE CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG 0x00020000
#DEFINE CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG 0x00040000
#DEFINE CERT_NCRYPT_KEY_SPEC 0xFFFFFFFF
#DEFINE PUBLICKEYBLOB 0x6
#DEFINE PRIVATEKEYBLOB 0x7
#DEFINE BCRYPT_RSA_ALGORITHM STRCONV("RSA" + CHR(0), 5)
#DEFINE BCRYPT_PRIVATE_KEY_BLOB STRCONV("PRIVATEBLOB" + CHR(0), 5)
#DEFINE LEGACY_RSAPRIVATE_BLOB STRCONV("CAPIPRIVATEBLOB" + CHR(0), 5)
#DEFINE BCRYPT_SHA1_ALGORITHM STRCONV("SHA1" + CHR(0), 5)
#DEFINE BCRYPT_PAD_PKCS1 0x00000002
#DEFINE BCRYPT_PAD_PSS 0x00000008
#DEFINE FORMAT_MESSAGE_FROM_SYSTEM 0x00001000

*----------------------- MAIN
LOCAL hStore  
pvPara = STRCONV("MY" + CHR(0), 5)  
hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, pvPara)
pCertContext=0
cTitulo=STRCONV("ATENCION: Seleccione su Certificado"+CHR(0),5)
cMensaje=STRCONV("Por favor haga clic en su certificado y Aceptar"+CHR(0),5)
pCertContext=CryptUIDlgSelectCertificateFromStore(hStore , 0, cTitulo, cMensaje, 1, 0, null)
IF pCertContext=0
=CertCloseStore(hStore, 0) 
RETURN .F.
ENDIF
tcParKey = GetPrivateKey(pCertContext)
IF EMPTY(tcParKey)
MESSAGEBOX("NO SE PUDO EXPORTAR EL PAR DE CLAVES, PROBABLEMENTE ESTA MARCADO COMO NO EXPORTABLE")
=CertCloseStore(hStore, 0)
RETURN .F.
ENDIF
tcDataSign = GetDigestValue("<ds:Inicio>Texto que se desea firmar</inicio>", "SHA1")
IF EMPTY(tcDataSign)
MESSAGEBOX("NO SE CREO EL HASH...NI MODO")
=CertCloseStore(hStore, 0)
RETURN .F.
ENDIF
tcSigned = GetSignHash(tcDataSign, tcParKey)
IF EMPTY(tcSigned)
MESSAGEBOX("NO SE FIRMO ESTA VAINA")
=CertCloseStore(hStore, 0)
RETURN .F.
ENDIF
? "Digest Value SHA1: " + TRANSFORM(tcDataSign)
?
? "Signature: " + STRCONV(tcSigned,13)
?
? "Longitud de firma: " + TRANSFORM(LEN(STRCONV(tcSigned,13)))
?
=CertCloseStore(hStore, 0)

PROCEDURE GetPrivateKey(pCertContext)
hCryptProv = 0
dwKeySpec  = 0
pfCFreProv = 0
nResCPrivK = CryptAcquireCertificatePrivateKey(pCertContext, CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, 0, @hCryptProv, @dwKeySpec, @pfCFreProv)
phUserKey = 0
nResCGUK = CryptGetUserKey(hCryptProv, dwKeySpec, @phUserKey)
pdwDataLen = 0
nRespEK = CryptExportKey(phUserKey , 0, PRIVATEKEYBLOB, 0, NULL, @pdwDataLen)
pbData = SPACE(pdwDataLen)
nRespEK = CryptExportKey(phUserKey , 0, PRIVATEKEYBLOB, 0, @pbData, @pdwDataLen)
IF EMPTY(pbData)
MESSAGEBOX("NO SE PUDO EXPORTAR EL PAR DE CLAVES")
ENDIF
RETURN pbData
ENDPROC

PROCEDURE GetDigestValue(tcData, tcHashAlg)
lnAlg = 0
nRespBCOAP = BCryptOpenAlgorithmProvider(@lnAlg, STRCONV(tcHashAlg,5)+CHR(0), NULL, 0)
IF nRespBCOAP<>0
MESSAGEBOX("ERROR AL ABRIR ALGORITMO")
RETURN ""
ENDIF
*----- Determinamos cuántos bytes necesitamos para almacenar el objeto hash
lnSizeObj = 0 
lnData = 0 
nRespNCGP = BCryptGetProperty(lnAlg, STRCONV("ObjectLength",5)+CHR(0), @lnSizeObj, 4, @lnData, 0)
IF nRespNCGP<>0
MESSAGEBOX("ERROR AL OBTENER PROPIEDAD DE ENCRIPTACION")
RETURN ""
ENDIF
*----- Determinamos la longitud de valor hash 
lnSizeHash = 0 
nRespNCGP = BCryptGetProperty(lnAlg, STRCONV("HashDigestLength",5)+CHR(0), @lnSizeHash, 4, @lnData, 0)
IF nRespNCGP<>0
MESSAGEBOX("ERROR AL OBTENER PROPIEDAD DE ENCRIPTACION")
RETURN ""
ENDIF
*----- Creamos un objeto Hash
LOCAL lnHash, lcHashObj 
lnHash = 0 
lcHashObj = SPACE(lnSizeObj) 
nRespBCCH = BCryptCreateHash(lnAlg, @lnHash, @lcHashObj, lnSizeObj, NULL, 0, 0)
IF nRespBCCH<>0
MESSAGEBOX("ERROR AL CREAR OBJETO HASH")
RETURN ""
ENDIF
*----- Para crear el valor hash agregamos datos al objeto hash. Puede repetir este paso según sea necesario
nLenData = LEN(tcData)
nRespBCHD = BCryptHashData(lnHash, tcData, nLenData, 0)
IF nRespBCHD<>0
nRespBCHD = BCryptHashData(lnHash, tcData, nLenData, 0)
IF nRespBCHD<>0
=GetMensajeError(nRespBCHD)
RETURN ""
ENDIF
ENDIF
*----- Indicamos al objeto hash que hemos terminado. El algoritmo ahora calcula el valor de hash y lo devuelve. 
lcHash = SPACE(lnSizeHash) 
=BCryptFinishHash(lnHash, @lcHash, lnSizeHash, 0)
IF lnAlg<>0
BCryptCloseAlgorithmProvider(lnAlg, 0) 
ENDIF 
IF lnHash<>0 
BCryptDestroyHash(lnHash) 
ENDIF
lcHash15 = STRCONV(lcHash,13) && HexBinary ~ 16 format 
RETURN lcHash15
ENDPROC

PROCEDURE GetSignHash(tcDataSign, tcParKey)
lcSigned = ""
lnAlg = 0
lnRes = BCryptOpenAlgorithmProvider(@lnAlg, BCRYPT_RSA_ALGORITHM, NULL, 0)
lnKey = 0
lnRes = BCryptImportKeyPair(lnAlg, 0, LEGACY_RSAPRIVATE_BLOB, @lnKey, tcParKey, LEN(tcParKey), 0)
IF lnRes = 0
lnAlgoString = HeapAlloc(GetProcessHeap(), 0, LEN(BCRYPT_SHA1_ALGORITHM))
IF lnAlgoString <> 0
SYS(2600, lnAlgoString, LEN(BCRYPT_SHA1_ALGORITHM), BCRYPT_SHA1_ALGORITHM)
lnSize = 0
lnRes = BCryptSignHash(lnKey, @lnAlgoString, tcDataSign, LEN(tcDataSign), NULL, 0, @lnSize, 8)
IF lnRes = 0
*---- Firmamos la cadena de datos
lcSigned = SPACE(lnSize)
lnRes = BCryptSignHash(lnKey, @lnAlgoString, tcDataSign, LEN(tcDataSign), @lcSigned, lnSize, @lnSize, 8)
IF lnRes = 0
*---- EXITO!
lcSigned = LEFT(lcSigned, lnSize)
ELSE
*---- fracaso
lcSigned = ""
ENDIF
ENDIF
HeapFree(GetProcessHeap(), 0, lnAlgoString)
ENDIF
BCryptDestroyKey(lnKey)
ENDIF
BCryptCloseAlgorithmProvider(lnAlg, 0)
RETURN lcSigned
ENDPROC

PROCEDURE GetMensajeError(tcNumError)
IF VARTYPE(tcNumError)=="N"
lnErrorCode = tcNumError
ELSE
lnErrorCode = GetLastError()
ENDIF
lpBuffer = SPACE(128)
=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 'WINERROR.H', lnErrorCode, 0, @lpBuffer, 128 , 0)
=MESSAGEBOX(lpBuffer, 16, "Error: " + TRANSFORM(lnErrorCode,"@0"))
ENDPROC

Fernando Mora

unread,
Jan 11, 2019, 12:44:28 AM1/11/19
to Comunidad de Visual Foxpro en Español
El anterior ejemplo hace que se abra la ventana de selección de certificado, esa es la función api, DECLARE LONG CryptUIDlgSelectCertificateFromStore IN Cryptui LONG hCertStore LONG hWnd STRING @pwszTitle, STRING @pwszDisplayString, LONG dwDontUseColumn, LONG dwFlags, STRING pvReserved. Super comodo para hacer todas las pruebas y avanzar sus investigaciones probando opciones. sáquenle el mayor provecho posible. También incluí un función para capturar errro en caso sea necesario.


vfp_select_Certificate.png


Diego Fazio

unread,
Jan 11, 2019, 6:12:25 AM1/11/19
to Comunidad de Visual Foxpro en Español
Hola, Fernando, estoy tratando de hacer algunas pruebas.
Primero empece reemplazando el texto a firmar por un archivo xml

tcDataSign = GetDigestValue(FILETOSTR("ARCHIVO.XML"), "SHA1")

El tema es que el resultado final siempre es una cadena que ocupa 172 bytes. Por lo que vi en el codigo el tamaño de la cadena a firmar la lee automaticamente dependiento del texto que le pongas. 

Gracias
Diego.

Fernando Mora

unread,
Jan 13, 2019, 1:47:01 PM1/13/19
to Comunidad de Visual Foxpro en Español
Hola Diego, el resultado de la cadena dependerá de la longitud de la clave pública de tu certificado, en el caso mio es e 2048, por eso el resultado del firmado de la cadena empleando el algoritmo de mi certificado y transformado a base64 es de 344, seguramente la longitud de la clave publica de tu certificado debe ser 1024 por eso el resultado te da la mitad, para corroborar esto, ingresar al almacén de certificados de Windows (en ejecutar de Windows pon "CERTMGR.MSC" y ejecuta) ingresa a la carpeta "Personal", "Certificados" has doble clic sobre tu certificado y revisa los detalles del mismo, revisa el campo "Clave publica" y ahí veras su longitud. 
Ahora te recomendaría que revises los enlaces que publique respecto a canonalización de datos, ahí explica que se debe firmar.

Saludos.

Diego

unread,
Jan 13, 2019, 8:18:19 PM1/13/19
to publice...@googlegroups.com
Si no tengo entendido mal, la longitud del archivo firmado depende del tamaño del archivo original. Por ahi estoy interpretando cualquier cosa respecto a lo que significa firmar un archivo. 
Yo utilizo actualmente openssl para firmar mis xml y el resultado que obtengo basicamente es tal cual te comente. Es asi o entendi mal?
Mi clave publica es 2048.

Diego

Luis suescún

unread,
Jan 14, 2019, 5:26:30 AM1/14/19
to publice...@googlegroups.com
Fernando Mora, muchas gracias por tu ofrecimiento y muy interesante tu aporte, por favor incluyeme para recibir la información.

Quedo atento 

Diego Fazio

unread,
Jan 14, 2019, 5:26:58 AM1/14/19
to Comunidad de Visual Foxpro en Español
Perdon. Quise decir mi clave "privada" es de 2048

Diego.

digicom...@gmail.com

unread,
Jan 20, 2019, 9:27:08 PM1/20/19
to Comunidad de Visual Foxpro en Español
Hola Fernando, estoy interesado en la documentación indicada, te agradecería me pases dicha documentación.

Fernando Mora

unread,
Jan 21, 2019, 5:10:10 PM1/21/19
to Comunidad de Visual Foxpro en Español
Hola Diego, si depende de que tipo de firmado desea obtener, el código de ejemplo que publique es para firma envuelta, donde se encripta partes del xml en hash con formatos sha1, sha256, etc y se firma ese Hash, no es para firmar el archivo xml entero, para eso se usa otras funciones (CryptSignMessage) y el proceso es diferente. 

almonts ( www.ontarioxb.es )

unread,
Jan 22, 2019, 7:17:25 PM1/22/19
to Comunidad de Visual Foxpro en Español
Me interesa. Mi correo es:
almonts(at)gmail(dot)com


Gaetano Quattrocchi

unread,
Jan 23, 2019, 2:16:20 AM1/23/19
to Comunidad de Visual Foxpro en Español
Me interesa. Mi correo es: 
Qgaet...@gmail.com
Grazie

Jose Antonio Blasco

unread,
Jan 23, 2019, 3:08:36 AM1/23/19
to Comunidad de Visual Foxpro en Español
Fernando, no se si estás enviando algo a los correos de los que
estamos interesados.
Si es así, se te debió pasar el mío y te ruego que me incluyas.
Mi correo es jabl...@gmail.com

Muchas gracias.

Jose A. Blasco
Zaragoza - España
Visual FoxPro 9 SP2


El mié., 23 ene. 2019 a las 8:16, Gaetano Quattrocchi
(<qgaet...@gmail.com>) escribió:

Juan Pablo Iparraguirre Iparraguirre

unread,
Jan 23, 2019, 3:38:51 AM1/23/19
to Comunidad de Visual Foxpro en Español
Estimado Fernando, es muy interesante esta propuesta, agradecería que me enviaras informacion sobre ello a mi correo iparrag...@gmail.com, Gracias mil.

arti...@gmail.com

unread,
Jan 25, 2019, 2:10:29 PM1/25/19
to Comunidad de Visual Foxpro en Español
Está muy interesante, hasta ahora he realizado un aplicativo para factura electrónica y la forma es por un software externo, me interesa, si es posible me gustaría que me remitieses una copia a arti...@gmail.com. Saludos y gracias

felix r.

unread,
Jan 25, 2019, 3:49:57 PM1/25/19
to Comunidad de Visual Foxpro en Español
Hola , te agradezco el aporte , y ruego me incluyas para enviarme la información a fvs...@gmail.com
Gracias

Edgar Acevedo

unread,
Jan 25, 2019, 4:02:18 PM1/25/19
to publicesvfoxpro
Mil gracias por tu ofrecimiento.  También quisiera ser incluido:  aper...@gmail.com

Saludos.

Ernesto Arias

unread,
Jan 25, 2019, 4:02:55 PM1/25/19
to publice...@googlegroups.com
Hola muchísimas gracias por tu aportación, por favor me puedes incluir.
Mi correo: ernesto.arias1@gmail

Saludos.

Arias Consultores - Soluciones en Sistemas
Ernesto Arias 
Celular. (044) 3333935927
 Oficina. (33) 31249135


Libre de virus. www.avast.com

micky khan

unread,
Jan 30, 2019, 5:56:47 PM1/30/19
to publice...@googlegroups.com
esta interesante.

--
  <_>
 (o o)
 (  °  )
<>-<>
Micky Khan

admcongar

unread,
Jan 31, 2019, 1:26:51 AM1/31/19
to publice...@googlegroups.com
Buena noches... estoy iniciando para la facturación electrónica desde visual fox, me podrías dar las pautas, actualmente ya tengo la facturación con código de control y QR, a este deseo añadir.



Enviado desde mi smartphone Samsung Galaxy.

-------- Mensaje original --------
Fecha: 25/1/19 15:10 (GMT-04:00)
A: Comunidad de Visual Foxpro en Español <publice...@googlegroups.com>
Asunto: [vfp] Re: Firmar XML 100% VFP sin DLL,

Está muy interesante, hasta ahora he realizado un aplicativo para factura electrónica y la forma es por un software externo, me interesa, si es posible me gustaría que me remitieses una copia a arti...@gmail.com. Saludos y gracias

Guillermo Giménez

unread,
Jan 31, 2019, 10:18:23 AM1/31/19
to Comunidad de Visual Foxpro en Español
Interesante tema. guille...@hotmail.com

Gracias

Miguel A.

unread,
Jan 31, 2019, 12:50:02 PM1/31/19
to Comunidad de Visual Foxpro en Español
Hola,
No sé si Fernando está enviando algo, o no. Personalmente le había pedido en su día que me enviase la información, pero no he recibido nada al respecto. Suponía que era debido a que con lo que había publicado ya era todo, pero observo que cada día más personas le siguen pidiendo información sobre este tema.
Si es posible, Fernando, publica el material y así acabas con este tipo de peticiones.
Saludos,
Miguel A.

Diego

unread,
Jan 31, 2019, 2:24:26 PM1/31/19
to publice...@googlegroups.com
Yo probe el codigo que publico y funciona perfecto. Creo que con eso alcanza. 

Diego

Irwin Rodriguez

unread,
Jan 31, 2019, 4:27:54 PM1/31/19
to publice...@googlegroups.com
Generalmente cuando es así es porque no es del todo gratis, el que quiere contrubuir simplemente sube su aporte y listo.

Saludos
--
Irwin Rodríguez
Analista Programador

+593 0994903424
Latacunga - Ecuador
"Un equipo solo son piezas que intercambias hasta que terminas el trabajo, es eficiente, funciona."

Francisco

unread,
Feb 4, 2019, 10:16:39 AM2/4/19
to Comunidad de Visual Foxpro en Español
Pues si, eso sería lo más lógico.

Rolando Ramos Lopez

unread,
Feb 4, 2019, 10:46:20 AM2/4/19
to publice...@googlegroups.com
Por favor enviar información. 
Muchas gracias. 

El jue., 10 ene. 2019 6:15 p. m., Kleinehassler <kleine...@gmail.com> escribió:
Fernando,

Enviar información al correo
di...@hassler.ec

Gracias

Fernando Mora

unread,
Feb 4, 2019, 11:25:54 AM2/4/19
to Comunidad de Visual Foxpro en Español

firmado.jpg

Saludos Compas, a los primero foristas que solicitaron les envié mensajes de Correo, explicando el tema, cuando aún estaba explorando estas opciones que desconocía de las apis de Windows con la idea de que también revisen el tema y vean que resultados pueden tener por su parte. Luego comencé a publicar aquí mismo en este hilo todo lo que logre investigar del tema y lo que se necesitan para firmar un Xml, (Firma envuelta por ahora) revisen los mensajes aquí publicado de mi parte. ¿Que necesitan para el firmado? Crear hash (DIgestValues)  de las regiones (URI) que  les soliciten según el modelo de esquema que necesiten. La función GetDigestValue() hace eso, el código esta qui expuesto, luego ese Hash (DigestValue) se lo firma con el par de clave, revisen el ejemplo ¿por lo menos ya lo intentaron? Con esto respondo a la duda de Irwin, "talvez no es del todo gratis", compa, no tienen que pagar veinte centavos por usar  el proveedor criptografico de windows, ES TOTALMENTE GRATIS. Con la dirección web que publique respecto a la canonicalización (palabra para hijue...) Solo tienen que armar su xml como cualquier archivo de texto, e ir colocando los resultados de los DigestValues y el firmado. Aquí les dejo un capture de un XML ya firmado usando el método 100% fox que esta en este hilo. Recién la semana pasada logre superar (con la ayuda de otro forista) el problema de la canonicalización, Ya logre firmar un XML enviar al SRI (Ecuador) y ya recibe la respuesta "AUTORIZADO", el firmado era lo único que supuestamente no se podía hacer con VFP, pero como ven SI SE PUEDE!... Ánimos compas, armen sus xml, SI VALE LA PENA INTENTARLO. En este momento estoy modificando mis aplicaciones para usar este método, en lugar de las librerias de net y java que son un asco.... controlar este proceso de forma directa con nuestro zorrito es lo mejor. Si necesitan ayuda en el proceso del armado de su xml con el firmado, me lo dicen, en que parte necesitan ayuda. Con excepción de Diego, nadie mas ha comentado si lo ha intentado. 

Fernando Mora

unread,
Feb 4, 2019, 11:36:01 AM2/4/19
to Comunidad de Visual Foxpro en Español
ACTUALIZACION:
Corrijan la linea del firmado cambiando el ultimo parámetro de 8 a 2, así debe quedar:
BCryptSignHash(lnKey, @lnAlgoString, tcDataSign, LEN(tcDataSign), NULL, 0, @lnSize, 2)


El viernes, 11 de enero de 2019, 0:26:31 (UTC-5), Fernando Mora escribió:
Aquí esta un ejemplo bastante completo, adicional a esto aprendan a canonalizar datos para realizar correctamente los digest-values y el firmado de los mismo.  Aquí hay un portal que es de gran ayuda, mejor explicado que ese portal imposible. Revisen la parte 1 y 2 de esta web. https://www.di-mgt.com.au/xmldsig2.html
 
Comenzamos el 2019 con pie derecho.... agradecer no cuesta nada. saludos a todos y bendiciones.


DECLARE LONG CertOpenStore IN crypt32;
LONG lpszStoreProvider,;
LONG dwEncodingType,;
LONG hCryptProv,;
LONG dwFlags,;
STRING pvPara
DECLARE LONG CertOpenSystemStore IN crypt32;
LONG hprov,;
STRING szSubsystemProtocol

DECLARE LONG CertCloseStore IN crypt32;
LONG hCertStore,;
LONG dwFlags

DECLARE LONG CryptUIDlgSelectCertificateFromStore IN Cryptui;
LONG hCertStore,;
LONG hWnd,;
STRING @pwszTitle,;
STRING @pwszDisplayString,;
LONG dwDontUseColumn,;
LONG dwFlags,;
STRING pvReserved

DECLARE LONG CryptAcquireCertificatePrivateKey IN crypt32;
LONG pCert,;
LONG dwFlags,;
LONG pvParameters,;
LONG @phCryptProvOrNCryptKey,;
LONG @pdwKeySpec,;
LONG @pfCallerFreeProvOrNCryptKey

DECLARE LONG CryptGetUserKey IN Advapi32;
LONG hProv,;
LONG dwKeySpec,;
LONG @phUserKey

DECLARE LONG CryptExportKey IN Advapi32;
LONG hKey,;
LONG hExpKey,;
LONG dwBlobType,;
LONG dwFlags,;
STRING @pbData,;
LONG @pdwDataLen
*------------------------- CNG 
DECLARE LONG BCryptOpenAlgorithmProvider IN BCrypt; 
LONG @phAlgorithm,;
STRING pszAlgId,; 
STRING pszImplementation,;
LONG dwFlags

DECLARE LONG BCryptImportKeyPair IN BCrypt; 
LONG hAlgorithm,;
LONG hImportKey,;
STRING pszBlobType,;
LONG @phKey,;
STRING pbInput,;
LONG cbInput,; 
LONG dwFlags 

DECLARE LONG BCryptSignHash IN BCrypt;  
LONG hKey,;
LONG @pPaddingInfo,;
STRING pbInput,;  
LONG cbInput,;
STRING @pbOutput,;
LONG cbOutput,;  
LONG @pcbResult,;
LONG dwFlags

DECLARE LONG BCryptGetProperty IN BCrypt;
LONG hObject,;
STRING pszProperty,;
LONG @pbOutput,;
LONG cbOutput,;
LONG @pcbResult,;
LONG dwFlags

DECLARE LONG BCryptCreateHash IN BCrypt; 
LONG hAlgorithm,;
LONG @phHash,;
STRING @pbHashObject,;
LONG cbHashObject,;
STRING pbSecret,;
LONG cbSecret,; 
LONG dwFlags

DECLARE LONG BCryptHashData IN BCrypt; 
LONG hHash,;
STRING pbInput,;
LONG cbInput,;
LONG dwFlags 

DECLARE LONG BCryptFinishHash IN BCrypt; 
LONG hHash,;
STRING @pbOutput,;
LONG cbOutput,;
LONG dwFlags 

DECLARE LONG BCryptDestroyHash IN BCrypt; 
LONG hHash 

DECLARE LONG BCryptDestroyKey IN BCrypt; 
LONG hKey

DECLARE LONG BCryptCloseAlgorithmProvider IN BCrypt; 
LONG hAlgorithm,;
LONG dwFlags

*------------------------ Kernel
DECLARE INTEGER GetLastError IN Kernel32

DECLARE LONG FormatMessage IN Kernel32;
  LONG dwFlags,;
  STRING @lpSource,;
  LONG dwMessageId,;
  LONG dwLanguageId,;
  STRING @lpBuffer,;
  LONG nSize,;
  LONG Arguments

DECLARE LONG GetProcessHeap IN Kernel32

DECLARE LONG HeapAlloc IN Kernel32;
LONG hHeap,;
LONG dwFlags,;
LONG dwBytes

DECLARE LONG HeapFree IN Kernel32;
LONG hHeap,;
LONG dwFlags,;
LONG lpMem
*------------------------ CONSTANTES 
#DEFINE CERT_STORE_PROV_SYSTEM 10 
#DEFINE CERT_SYSTEM_STORE_CURRENT_USER 0x00010000
#DEFINE CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG 0x00010000
#DEFINE CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG 0x00020000
#DEFINE CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG 0x00040000
#DEFINE CERT_NCRYPT_KEY_SPEC 0xFFFFFFFF
#DEFINE PUBLICKEYBLOB 0x6
#DEFINE PRIVATEKEYBLOB 0x7
#DEFINE BCRYPT_RSA_ALGORITHM STRCONV("RSA" + CHR(0), 5)
#DEFINE BCRYPT_PRIVATE_KEY_BLOB STRCONV("PRIVATEBLOB" + CHR(0), 5)
#DEFINE LEGACY_RSAPRIVATE_BLOB STRCONV("CAPIPRIVATEBLOB" + CHR(0), 5)
#DEFINE BCRYPT_SHA1_ALGORITHM STRCONV("SHA1" + CHR(0), 5)
#DEFINE BCRYPT_PAD_PKCS1 0x00000002
#DEFINE BCRYPT_PAD_PSS 0x00000008
#DEFINE FORMAT_MESSAGE_FROM_SYSTEM 0x00001000

*----------------------- MAIN
LOCAL hStore  
pvPara = STRCONV("MY" + CHR(0), 5)  
hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, pvPara)
pCertContext=0
cTitulo=STRCONV("ATENCION: Seleccione su Certificado"+CHR(0),5)
cMensaje=STRCONV("Por favor haga clic en su certificado y Aceptar"+CHR(0),5)
pCertContext=CryptUIDlgSelectCertificateFromStore(hStore , 0, cTitulo, cMensaje, 1, 0, null)
IF pCertContext=0
=CertCloseStore(hStore, 0) 
RETURN .F.
ENDIF
tcParKey = GetPrivateKey(pCertContext)
IF EMPTY(tcParKey)
MESSAGEBOX("NO SE PUDO EXPORTAR EL PAR DE CLAVES, PROBABLEMENTE ESTA MARCADO COMO NO EXPORTABLE")
=CertCloseStore(hStore, 0)
RETURN .F.
ENDIF
tcDataSign = GetDigestValue("<ds:Inicio>Texto que se desea firmar</inicio>", "SHA1")
IF EMPTY(tcDataSign)
MESSAGEBOX("NO SE CREO EL HASH...NI MODO")
=CertCloseStore(hStore, 0)
RETURN .F.
ENDIF
tcSigned = GetSignHash(tcDataSign, tcParKey)
IF EMPTY(tcSigned)
MESSAGEBOX("NO SE FIRMO ESTA VAINA")
=CertCloseStore(hStore, 0)
RETURN .F.
ENDIF
? "Digest Value SHA1: " + TRANSFORM(tcDataSign)
?
? "Signature: " + STRCONV(tcSigned,13)
?
? "Longitud de firma: " + TRANSFORM(LEN(STRCONV(tcSigned,13)))
?
=CertCloseStore(hStore, 0)

PROCEDURE GetPrivateKey(pCertContext)
hCryptProv = 0
dwKeySpec  = 0
pfCFreProv = 0
nResCPrivK = CryptAcquireCertificatePrivateKey(pCertContext, CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, 0, @hCryptProv, @dwKeySpec, @pfCFreProv)
phUserKey = 0
nResCGUK = CryptGetUserKey(hCryptProv, dwKeySpec, @phUserKey)
pdwDataLen = 0
nRespEK = CryptExportKey(phUserKey , 0, PRIVATEKEYBLOB, 0, NULL, @pdwDataLen)
pbData = SPACE(pdwDataLen)
nRespEK = CryptExportKey(phUserKey , 0, PRIVATEKEYBLOB, 0, @pbData, @pdwDataLen)
IF EMPTY(pbData)
MESSAGEBOX("NO SE PUDO EXPORTAR EL PAR DE CLAVES")
ENDIF
RETURN pbData
ENDPROC

PROCEDURE GetDigestValue(tcData, tcHashAlg)
lnAlg = 0
nRespBCOAP = BCryptOpenAlgorithmProvider(@lnAlg, STRCONV(tcHashAlg,5)+CHR(0), NULL, 0)
IF nRespBCOAP<>0
MESSAGEBOX("ERROR AL ABRIR ALGORITMO")
RETURN ""
ENDIF
*----- Determinamos cuántos bytes necesitamos para almacenar el objeto hash
lnSizeObj = 0 
lnData = 0 
nRespNCGP = BCryptGetProperty(lnAlg, STRCONV("ObjectLength",5)+CHR(0), @lnSizeObj, 4, @lnData, 0)
IF nRespNCGP<>0
MESSAGEBOX("ERROR AL OBTENER PROPIEDAD DE ENCRIPTACION")
RETURN ""
ENDIF
*----- Determinamos la longitud de valor hash 
lnSizeHash = 0 
nRespNCGP = BCryptGetProperty(lnAlg, STRCONV("HashDigestLength",5)+CHR(0), @lnSizeHash, 4, @lnData, 0)
IF nRespNCGP<>0
MESSAGEBOX("ERROR AL OBTENER PROPIEDAD DE ENCRIPTACION")
RETURN ""
ENDIF
*----- Creamos un objeto Hash
LOCAL lnHash, lcHashObj 
lnHash = 0 
lcHashObj = SPACE(lnSizeObj) 
nRespBCCH = BCryptCreateHash(lnAlg, @lnHash, @lcHashObj, lnSizeObj, NULL, 0, 0)
IF nRespBCCH<>0
MESSAGEBOX("ERROR AL CREAR OBJETO HASH")
RETURN ""
ENDIF
*----- Para crear el valor hash agregamos datos al objeto hash. Puede repetir este paso según sea necesario
nLenData = LEN(tcData)
nRespBCHD = BCryptHashData(lnHash, tcData, nLenData, 0)
IF nRespBCHD<>0
nRespBCHD = BCryptHashData(lnHash, tcData, nLenData, 0)
IF nRespBCHD<>0
=GetMensajeError(nRespBCHD)
RETURN ""
ENDIF
ENDIF
*----- Indicamos al objeto hash que hemos terminado. El algoritmo ahora calcula el valor de hash y lo devuelve. 
lcHash = SPACE(lnSizeHash) 
=BCryptFinishHash(lnHash, @lcHash, lnSizeHash, 0)
IF lnAlg<>0
BCryptCloseAlgorithmProvider(lnAlg, 0) 
ENDIF 
IF lnHash<>0 
BCryptDestroyHash(lnHash) 
ENDIF
lcHash15 = STRCONV(lcHash,13) && HexBinary ~ 16 format 
RETURN lcHash15
ENDPROC

PROCEDURE GetSignHash(tcDataSign, tcParKey)
lcSigned = ""
lnAlg = 0
lnRes = BCryptOpenAlgorithmProvider(@lnAlg, BCRYPT_RSA_ALGORITHM, NULL, 0)
lnKey = 0
lnRes = BCryptImportKeyPair(lnAlg, 0, LEGACY_RSAPRIVATE_BLOB, @lnKey, tcParKey, LEN(tcParKey), 0)
IF lnRes = 0
lnAlgoString = HeapAlloc(GetProcessHeap(), 0, LEN(BCRYPT_SHA1_ALGORITHM))
IF lnAlgoString <> 0
SYS(2600, lnAlgoString, LEN(BCRYPT_SHA1_ALGORITHM), BCRYPT_SHA1_ALGORITHM)
lnSize = 0
lnRes = BCryptSignHash(lnKey, @lnAlgoString, tcDataSign, LEN(tcDataSign), NULL, 0, @lnSize, 8)
IF lnRes = 0
*---- Firmamos la cadena de datos
lcSigned = SPACE(lnSize)
lnRes = BCryptSignHash(lnKey, @lnAlgoString, tcDataSign, LEN(tcDataSign), @lcSigned, lnSize, @lnSize, 8)
IF lnRes = 0
*---- EXITO!
lcSigned = LEFT(lcSigned, lnSize)
ELSE
*---- fracaso
lcSigned = ""
ENDIF
ENDIF
HeapFree(GetProcessHeap(), 0, lnAlgoString)
ENDIF
BCryptDestroyKey(lnKey)
ENDIF
BCryptCloseAlgorithmProvider(lnAlg, 0)
RETURN lcSigned
ENDPROC

PROCEDURE GetMensajeError(tcNumError)
IF VARTYPE(tcNumError)=="N"
lnErrorCode = tcNumError
ELSE
lnErrorCode = GetLastError()
ENDIF
lpBuffer = SPACE(128)
=FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 'WINERROR.H', lnErrorCode, 0, @lpBuffer, 128 , 0)
=MESSAGEBOX(lpBuffer, 16, "Error: " + TRANSFORM(lnErrorCode,"@0"))
ENDPROC

Diego Fazio

unread,
Feb 4, 2019, 12:21:33 PM2/4/19
to Comunidad de Visual Foxpro en Español
Lei gran parte de la documentacion que figura en la Url que compartiste y la verdad es muy interesante. En mi caso en particular lo que necesitaria firmar es un xml completo, y luego pasarlo a base64. Aca en Argentina se maneja de esa manera. El tema es que utilizando crypt32 de Microsoft para hacer esto es un tanto mas complejo ya que para poder invocar a la funcion CryptSignMessage necesito declarar algunas estructuras dentro de estructuras que son un tanto complejas. Si llegas a saber algo respecto a esto desde VFP o VB6 que seria lo mismo avisaaaa...
Igual no me voy a dar por vencido asi nomas....

Gracias
Diego.

Fernando Mora

unread,
Feb 4, 2019, 12:58:10 PM2/4/19
to Comunidad de Visual Foxpro en Español
Diego, a ese modelo se lo conoce como "detached signature" (Firma separada) creo, es así?, estoy revisando la documentación de MS para ese tipo de firmado, en cuanto tenga un ejemplo traducido a VFP te paso el código. Creo que para armar esas estructura se usa cadenas de bytes con BINTOC y luego Codificar la cadena con CryptEncondeObjectEx. hay que probar, Todo es cuestión que logremos traducir una estructura correctamente, y con eso nos podemos guiar para todas las funciones que usen esas estructuras complejas. 

Esteban H.

unread,
Feb 5, 2019, 6:57:58 PM2/5/19
to publice...@googlegroups.com

Hola Fernando.

 

Agregando Info a lo q quiere hacer Diego, la firma de AFIP en Argentina, a parte de ser en b64, el xml contiene un certificado + una clave privada de 2048, lo veo complicado de resolver, pero… todo es posible.

Ojalá se pudiera sacar, actualmente esto lo resuelve en forma excelente el OpenSsl, pero sería genial resolverlo desde VFP con Apis.

 

Saludos

Esteban

Fernando Mora

unread,
Feb 6, 2019, 2:13:57 PM2/6/19
to Comunidad de Visual Foxpro en Español
Saludos compas, Diego  y Esteban revisen este código de Detached Signature, se supone que CRYPT_SIGN_MESSAGE_PARA lo conforman 17 parámetros, de los cuales 6 son obligatorios y los 11 restantes opcionales, salvo el quinto parámetro (pvHashAuxInfo), los 7 primeros son indispensables, el resto opcionales y pueden ser null, la longitud de la cadena deberia ser 68 (17 x 4) pero he probado que funciona con 44. Esto da como resultado una cadena ASN1 que luego se la debe convertir a b64. Otra cosa, use la API RtlMoveMemory para llenar los punteros de memorias creados, porque con SYS(2600) de fox luego de uno o dos firmados, se colgaba el programa. No se a que se deba, ayuden a investigar porque, tambien falta traducir CryptVerifyMessageSignature, hay un ejemplo en la documentación de MS, manos a la obra

*--------------- CryptoApi CSP
DECLARE LONG CertOpenSystemStore IN crypt32;
LONG hprov,;
STRING szSubsystemProtocol

DECLARE LONG CryptUIDlgSelectCertificateFromStore IN Cryptui;
LONG hCertStore,;
LONG hWnd,;
STRING @pwszTitle,;
STRING @pwszDisplayString,;
LONG dwDontUseColumn,;
LONG dwFlags,;
STRING pvReserved

DECLARE LONG CryptSignMessage IN Crypt32;
  STRING @pSignPara,;  
  LONG fDetachedSignature,;  
  INTEGER cToBeSigned,;  
  STRING @rgpbToBeSigned,;  
  STRING @rgcbToBeSigned,;  
  STRING @pbSignedBlob,;  
  LONG @pcbSignedBlob

DECLARE LONG CryptVerifyMessageSignature IN Crypt32;
STRING @pVerifyPara,;
LONG dwSignerIndex,;
STRING pbSignedBlob,;
LONG cbSignedBlob,;
STRING @pbDecoded,;
LONG pcbDecoded,;
LONG ppSignerCert

DECLARE LONG CertFreeCertificateContext IN Crypt32;  
LONG pCertContext

DECLARE LONG CertCloseStore IN Crypt32;    
LONG hCertStore,;    
LONG dwFlag

*--------------- Kernel
DECLARE LONG GetLastError IN Kernel32

DECLARE LONG FormatMessage IN Kernel32;
  LONG dwFlags,;
  STRING @lpSource,;
  LONG dwMessageId,;
  LONG dwLanguageId,;
  STRING @lpBuffer,;
  LONG nSize,;
  LONG Arguments

DECLARE LONG GetProcessHeap IN Kernel32

DECLARE LONG HeapAlloc IN Kernel32;
LONG hHeap,;
LONG dwFlags,;
LONG dwBytes

DECLARE LONG HeapFree IN Kernel32;
LONG hHeap,;
LONG dwFlags,;
LONG lpMem

DECLARE RtlMoveMemory IN Kernel32;
LONG Destination,;
STRING @Source,;
LONG Length

*------------ Constantes
#DEFINE PKCS_7_ASN_ENCODING 0x00010000  
#DEFINE X509_ASN_ENCODING 0x00000001
#DEFINE FORMAT_MESSAGE_FROM_SYSTEM 0x00001000

*------------ Main
cFileXML=GETFILE("XML", "Xml a Firmar")
IF EMPTY(cFileXML)
RETURN .F.
ENDIF
hStore = CertOpenSystemStore(0, "MY")
pCertContext=CryptUIDlgSelectCertificateFromStore(hStore , 0, NULL, NULL, 1, 0, NULL)
IF pCertContext=0
=CertCloseStore(hStore, 0) 
RETURN .F.
ENDIF
lcSignature=GetSignXml(cFileXML, pCertContext)
IF !EMPTY(lcSignature)
? STRCONV(lcSignature,13)
lbReturn=.T.
ELSE
lbReturn=.F.
ENDIF
=CertCloseStore(hStore, 0)
=CertFreeCertificateContext(pCertContext)
RETURN lbReturn

*--------------- Región de Procedures
PROCEDURE GetSignXml(tcPathFileXml, tpCertContext)
*SET STEP ON
lcCertInfo = GetCertInfoStructure(tpCertContext)
lcOID = GetSignatureAlgorithm(lcCertInfo)
IF EMPTY(lcOID)
MESSAGEBOX("No se encontró un algoritmo de firma en este certificado", 16, _SCREEN.Caption)
RETURN ""
ENDIF
*----- Creamos Puntero de Memoria y colocamos el algoritmo de firma
lpOID = HeapAlloc(GetProcessHeap(), 0, LEN(lcOID))
RtlMoveMemory(lpOID, @lcOID, LEN(lcOID))
*----- Creamos Puntero de Memoria y colocamos el Certificado
lpCertificate = 0h + BINTOC(tpCertContext,"4RS")
lpRgpMsgCert = HeapAlloc(GetProcessHeap(), 0, LEN(lpCertificate))
RtlMoveMemory(lpRgpMsgCert, @lpCertificate, LEN(lpCertificate))
*----- Armamos la Estructura CRYPT_SIGN_MESSAGE_PARA
lnLenSP = 68 && (17 estructuras * 4 )
nMsgCert = 1
cbSize = BINTOC(lnLenSP,"4RS")
dwMsgEncodingType = BINTOC(BITOR(PKCS_7_ASN_ENCODING, X509_ASN_ENCODING),"4RS")
pSigningCert = BINTOC(tpCertContext,"4RS")
HashAlgorithm = BINTOC(lpOID,"4RS")
pvHashAuxInfo = REPLICATE(CHR(0), 12) && Se probo 4,8,12, funciona 12 ¿¿??
cMsgCert = BINTOC(nMsgCert,"4RS")
rgpMsgCert = BINTOC(lpRgpMsgCert,"4RS")
*----- Resto de estructuras opcionales, rellenar con CHR(0)
*cMsgCrl, rgpMsgCrl, cAuthAttr, rgAuthAttr, cUnauthAttr, rgUnauthAttr, 
*dwFlags, dwInnerContentType, HashEncryptionAlgorithm, pvHashEncryptionAuxInfo
pSignPara = 0h + cbSize + dwMsgEncodingType + pSigningCert + HashAlgorithm + pvHashAuxInfo + cMsgCert + rgpMsgCert
nlenpSign = LEN(pSignPara)
pSignPara = pSignPara + REPLICATE(CHR(0), lnLenSP-nlenpSign)
*----- Cargamos el archivo Xml a Firmar
lcFileXmlIn = tcPathFileXml
lcXmlString = FILETOSTR(lcFileXmlIn)
*----- Colocamos la cadena Xml dentro de un puntero
lpByToSigned = HeapAlloc(GetProcessHeap(), 0, LEN(lcXmlString))
RtlMoveMemory(lpByToSigned, @lcXmlString, LEN(lcXmlString))
*----- Procedemos a Firmar el Xml
rgpbToBeSigned = 0h + BINTOC(lpByToSigned, "4RS")
lnXmlStrLen = LEN(lcXmlString)
rgcbToBeSigned = 0h + BINTOC(lnXmlStrLen, "4RS")
pcbSignedBlob = 0
pbSignedBlob = ""
IF CryptSignMessage(@pSignPara, 1, 1, @rgpbToBeSigned, @rgcbToBeSigned, @pbSignedBlob, @pcbSignedBlob)=0
pbSignedBlob = SPACE(pcbSignedBlob)
IF CryptSignMessage(@pSignPara, 1, 1, @rgpbToBeSigned, @rgcbToBeSigned, @pbSignedBlob, @pcbSignedBlob)=0
=GetMensajeError()
ENDIF
ENDIF
*----- Liberamos los punteros de memoria
HeapFree(GetProcessHeap(), 0, lpByToSigned)
HeapFree(GetProcessHeap(), 0, lpRgpMsgCert)
HeapFree(GetProcessHeap(), 0, lpOID)
RETURN pbSignedBlob
ENDPROC

PROCEDURE GetCertInfoStructure(pCertContext)
lcCERT_CONTEXT = SYS(2600, pCertContext, 20)
lcCertInfo = SUBSTR(lcCERT_CONTEXT, 13, 4)
lpCertInfo = CTOBIN(lcCertInfo, "4RS")
lcCERT_INFO = SYS(2600, lpCertInfo, 112)
RETURN lcCERT_INFO
ENDPROC

PROCEDURE GetSignatureAlgorithm(lcCERT_INFO)
lcSignAlgoritm = SUBSTR(lcCERT_INFO,13,12)
lpSignAlgoritm1 = CTOBIN(SUBSTR(lcSignAlgoritm, 1, 4),"4RS")
lpSignAlgoritm2 = CTOBIN(SUBSTR(lcSignAlgoritm, 5, 4),"4RS")
lpSignAlgoritm3 = CTOBIN(SUBSTR(lcSignAlgoritm, 9, 4),"4RS")
lStrSignAlgorit = SYS(2600, lpSignAlgoritm1, 25)
lStrParaAlgorit = SYS(2600, lpSignAlgoritm3, lpSignAlgoritm2)
cStrSignAlgorit = STRTRAN(STREXTRACT(lStrSignAlgorit,LEFT(lStrSignAlgorit,1),CHR(0),1,4),CHR(0),"")
cStrParaAlgorit = TRANSFORM(STRCONV(lStrParaAlgorit,15))
RETURN cStrSignAlgorit

Diego Fazio

unread,
Feb 7, 2019, 11:30:31 AM2/7/19
to Comunidad de Visual Foxpro en Español
Ahi lo probe. El archivo al parecer lo firma perfecto. Pero el webservice me devuelve "Firma inv&#xE1;lida o algoritmo no soportado"

Diego.

Fernando Mora

unread,
Feb 7, 2019, 4:12:16 PM2/7/19
to Comunidad de Visual Foxpro en Español
Cuando mande los primero xml firmados al SRI también me devolvía  "firma alterada", me toco revisar milimetro por milimetro la cadena xml, pensé que algo no estaba bien en la canonicalización, tarde un buen rato en darme cuenta que lo que estaba mal era el ultimo parámetro de BCryptSignHash, era 2 en lugar de 8... no toca mas que ir probando parámetros, ir cambiando hasta encontrar el correcto. por ahora lo que se me ocurre es que la cadena podría ser necesario que termine en nulo, prueba eso.
Diego agrégale un +CHR(0) a la cadena Xml, así: lcXmlString = FILETOSTR(lcFileXmlIn)+CHR(0), prueba, revisa parámetros si lograr resultados positivos, colabora publicando el código, suerte. 

Diego Fazio

unread,
Feb 8, 2019, 4:55:45 PM2/8/19
to Comunidad de Visual Foxpro en Español
Fernando, creo que el problema esta en donde se declara la variable "HashAlgorithm" en la cual por lo que lei se define el algoritmo de firmado que tendria que ser SHA1+RSA. La estructura de HashAlgorithm seria asi...

typedef struct _CRYPT_ALGORITHM_IDENTIFIER { LPSTR pszObjId; CRYPT_OBJID_BLOB Parameters; } CRYPT_ALGORITHM_IDENTIFIER, *PCRYPT_ALGORITHM_IDENTIFIER;

en donde pszObjId le deberia asignar esto para utilizar el firmado que necesito...

szOID_RSA_SHA1RSA   -> "1.2.840.113549.1.1.5" 

y en los parametros definirlos como 0(DWORD) y 0(byte)

Por eso es que en pvHashAuxInfo estas llenandolo con 12 CHR(0) cuando deberia ser void suplantando lo que puse arriba que falta. 
El tema es como defino esa estructura. Me cuelgo con el LPSTR que no se como pasarlo a la var pSignPara 

Diego,