Firmar XML 100% VFP sin DLL,

7,475 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,

Fernando Mora

unread,
Feb 11, 2019, 10:49:01 AM2/11/19
to Comunidad de Visual Foxpro en Español
Diego, favor revisa estos ajustes, y me avisas. Pilas con la cadena Xml, pueda que sea necesario que sea terminada en nulo (CHR(0)), o no, abría que probar. 


? "Longitud de firma en b64: " + TRANSFORM(LEN(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
*----- Creamos Puntero de Memoria, colocamos el algoritmo de firma y armamos estructura CRYPT_ALGORITHM_IDENTIFIER
lcOID = "1.2.840.113549.1.1.5" && szOID_RSA_SHA1RSA
lpOID = HeapAlloc(GetProcessHeap(), 0, LEN(lcOID))
RtlMoveMemory(lpOID, @lcOID, LEN(lcOID))
CRYPT_ALGORITHM_IDENTIFIER = 0h + BINTOC(lpOID,"4RS") + BINTOC(0, "4RS")
*----- 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 = CRYPT_ALGORITHM_IDENTIFIER
*pvHashAuxInfo = REPLICATE(CHR(0), 8) && Se probo 4,8,12, ¿¿??
pvHashAuxInfo = BINTOC(0, "4RS") + BINTOC(0, "4RS")
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 teminado en NULO (probar)
lcFileXmlIn = tcPathFileXml
lcXmlString = FILETOSTR(lcFileXmlIn)+CHR(0)

Diego Fazio

unread,
Feb 12, 2019, 12:14:54 PM2/12/19
to Comunidad de Visual Foxpro en Español
Hola Fernando, Obtengo el mismo error del webservice. Me dice que "Firma invalida o algoritmo no soportado"
Lo del chr(0) al final del xml ya lo habia probado. Tambien use otros algoritmos de firma y tampoco hubo caso. Algunos me tiraba directamente error al ejecutar el proyecto y otro pasaban pero el webservice decia que era invalido.
Para mi que no esta bien armada la estructura del CRYPT_SIGN_MESSAGE_PARA

Diego.

admcongar

unread,
Feb 12, 2019, 3:19:07 PM2/12/19
to publice...@googlegroups.com
Muy interesante... estoy por iniciar el sistema de facturacion electrónica en vfp9 y sus conocimientos me vendrá muy bien... por favor una guía de su parte me vendría bien y estaré muy agradecido. 
Estaré atento. 



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

Alexander Mendoza Cuevas

unread,
Feb 13, 2019, 8:07:32 AM2/13/19
to publice...@googlegroups.com
Buenos días, muchas gracias por el aporte, también estoy como varios incursionando en la facturación electrónica, me gustaría recibir mas información de ser posible, desde ya muchas gracias.


Agradecido.
--

------------------------------------
Alexander Mendoza Cuevas
Cel.: 0984-86.50.11

Luisjdlca D

unread,
Feb 13, 2019, 12:10:07 PM2/13/19
to publice...@googlegroups.com
ok amigo si funciona amigos foxeros...
pero habra una mejora por fabor
que no pida e certificado 
para pasarle automaticamente el certificado y la ruta
ya q siempre me pide el certificado

Diego Fazio

unread,
Feb 13, 2019, 12:23:33 PM2/13/19
to Comunidad de Visual Foxpro en Español
Yo utilice la funcion PFXImportCertStore para abrir el .pfx con su clave creando asi el hStore. Y luego usas CertFindCertificateInStore para crear el pCertContext. Tengo que depurar un error que me tira pero estoy casi seguro que es por ahi que se hace. De esa manera abre el certificado directamente sin necesidad el usuario tenga que seleccionarlo.

Igual estoy colgado ahi con la firma envuelta. Vos estas utilizando la firma para Argentina?

Diego.

arti...@gmail.com

unread,
Feb 13, 2019, 3:00:38 PM2/13/19
to Comunidad de Visual Foxpro en Español
¿ Alguien me puede decir donde puedo conseguir esta utilidad ? <b>Saludos</b> y gracias

Diego Fazio

unread,
Feb 14, 2019, 7:53:33 AM2/14/19
to Comunidad de Visual Foxpro en Español
Fernando, BUENAS NOTICIAS!!! Te cuento que despues de probar y probar diferentes algoritmos de firmado y alguna que otra modificacion al codigo que me pasaste ya pude hacer la firma RSA+SHA1 necesaria para Argentina todo desde Fox utilizando la libreria crypt32 que ya viene incluida en Windows. Y FUNCIONO PERFECTO!!! Conclusion... en un equipo con Windows no seria necesario implementar ninguna libreria de terceros para poder realizar la firma.

Ahora estoy trabajando en algo que comento alguien mas arriba que es abrir el certificado directamente sin que te lo pida seleccionar primero. 
Estoy probando con PFXImportCertStore y luego con CertFindCertificateInStore. Vos lo hiciste?

Actualmente en realidad trabajo en Harbour y utilizo OpenSSL desde codigo fuente para hacer esto y va perfecto. Mas que nada me dio curiosidad lo que habias planteado de hacerlo con Crypt32. Ahora me toca la ardua tarea de traducir esto de VFP a harbour y ver que pasa.

Gracias
Diego.



El lunes, 11 de febrero de 2019, 12:49:01 (UTC-3), Fernando Mora escribió:

Jose Antonio Blasco

unread,
Feb 14, 2019, 10:05:03 AM2/14/19
to Comunidad de Visual Foxpro en Español
Diego, podrías compartir el código con el que has conseguido firmar?.

Gracias anticipadas.

Un saludo.

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


El jue., 14 feb. 2019 a las 13:53, Diego Fazio
(<diego...@gmail.com>) escribió:

Diego Fazio

unread,
Feb 14, 2019, 11:57:47 AM2/14/19
to Comunidad de Visual Foxpro en Español
Bueno. Ahi pude depurar un error que me daba al tratar de abrir el .pfx directamente desde el archivo con su clave. Puse tambien un verificador de clave con su certificado. Quedo Perfecto!!

Por ahi hay parte de codigo que esta de mas, no lo limpie todavia. Es el bruto con el que estuve trabajando
Cualquier cosa no duden en consultar 
Gracias Fernando!!

Quedo asi...

*--------------- 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

*AGREGADO POR MI

DECLARE LONG PFXImportCertStore IN Crypt32;
  STRING  @pfx,;
STRING  szPassword,;
LONG dwFlags

DECLARE INTEGER PFXVerifyPassword IN Crypt32;
  STRING pfx,;
STRING  szPassword,;
LONG dwFlags

DECLARE INTEGER CertFindCertificateInStore IN crypt32;
INTEGER hCertStore,;
LONG dwCertEncodingType,;
LONG dwFindFlags,;
LONG dwFindType,;
INTEGER pvFindPara,;
INTEGER pPrevCertContext  

#DEFINE szOID_NIST_sha512 "2.16.840.1.101.3.4.2.3"


*------------ Main
clear
cFileXML="archivo.xml"
IF EMPTY(cFileXML)
RETURN .F.
ENDIF


*!* **selecciona Certificado a traves de la interfase de usuario
*!* hStore = CertOpenSystemStore(0, "MY")
*!* pCertContext=CryptUIDlgSelectCertificateFromStore(hStore , 0, NULL, NULL, 1, 0, NULL)


*Seleccion Certificado directamente desde un archivo
*inicio
CerPFX  = FILETOSTR("C:\PROYECTOS\NEWFPRG\SOURCE\WEBSERVICE\CERT\CERT.PFX")
clave = "123456"
clave = STRCONV(clave,5)+CHR(0)
cbData = LEN(CerPFX)
pbData = HeapAlloc(GetProcessHeap(), 0, cbData)
RtlMoveMemory(pbData, @CerPFX, cbData)
pPFX = 0h + BINTOC(cbData,"4RS")+ BINTOC(pbData,"4RS") 

if PFXVerifyPassword(@pPFX,clave,0)=0
MESSAGEBOX( "La clave no es correcta" )
HeapFree(GetProcessHeap(), 0, pbData)
RETURN
ENDIF
hStore = PFXImportCertStore(@pPFX, clave, 0)
pCertContext = CertFindCertificateInStore(hStore,BITOR(PKCS_7_ASN_ENCODING, X509_ASN_ENCODING),0, 0, 0, 0) 
*fin 


IF pCertContext=0
=CertCloseStore(hStore, 0) 
RETURN .F.
ENDIF
lcSignature=GetSignXml(cFileXML, pCertContext)
IF !EMPTY(lcSignature)
STRTOFILE(lcSignature,"C:\proyectos\newfprg\source\webservice\tickets\TRA_wsfe.TMP")
? lcSignature
lbReturn=.T.
ELSE
lbReturn=.F.
ENDIF
=CertCloseStore(hStore, 0)
=CertFreeCertificateContext(pCertContext)
RETURN lbReturn

*--------------- Región de Procedures
PROCEDURE GetSignXml(tcPathFileXml, tpCertContext)
*SET STEP ON
*----- Creamos Puntero de Memoria, colocamos el algoritmo de firma y armamos estructura CRYPT_ALGORITHM_IDENTIFIER
lcOID = szOID_NIST_sha512
lpOID = HeapAlloc(GetProcessHeap(), 0, LEN(lcOID))
RtlMoveMemory(lpOID, @lcOID, LEN(lcOID)) 
CRYPT_ALGORITHM_IDENTIFIER = 0h + BINTOC(lpOID,"4RS") + BINTOC(0, "4RS")
*----- 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 = CRYPT_ALGORITHM_IDENTIFIER + BINTOC(0, "4RS") + BINTOC(0, "4RS")
pvHashAuxInfo = 0h
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) + 1 
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) + 1)
RtlMoveMemory(lpByToSigned, @lcXmlString, LEN(lcXmlString) + 1)
*----- Procedemos a Firmar el Xml
rgpbToBeSigned = 0h + BINTOC(lpByToSigned, "4RS")
lnXmlStrLen = LEN(lcXmlString) 
rgcbToBeSigned = 0h + BINTOC(lnXmlStrLen, "4RS")
pcbSignedBlob = 0
pbSignedBlob = ""
IF CryptSignMessage(@pSignPara, 0, 1, @rgpbToBeSigned, @rgcbToBeSigned, @pbSignedBlob , @pcbSignedBlob)=0
pbSignedBlob = SPACE(pcbSignedBlob)
IF CryptSignMessage(@pSignPara, 0, 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 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.

Fernando Mora

unread,
Feb 19, 2019, 2:46:05 PM2/19/19
to Comunidad de Visual Foxpro en Español
Felicitaciones Diego, el premio a la perseverancia, ya tenemos una nueva manera de realizar el firmado, EXCELENTE. Y enhorabuena que ya uses otras apis, como la importación de PFX/P12. te recomendaría que le agregues una seguridad mas a la validación del certificado a importar, se trata de verificar que el archivo sea un blob pfx valido. Usa DECLARE LONG PFXIsPFXBlob IN Crypt32 STRING pPFX, también la constante para que el certificado sea exportable, CRYPT_EXPORTABLE. Este es mi código de importación de PFX/P12

PROCEDURE GetImportPFX
PARAMETERS tcArchivoPfx AS String, tcPassword AS String
IF EMPTY(tcArchivoPfx)
MESSAGEBOX("Ingrese un archivo de certificado digital para importar", 16, _SCREEN.Caption)
RETURN 0
ENDIF
*----- Armamos la Estructura CRYPT_DATA_BLOB 
StrPfx = FILETOSTR(tcArchivoPfx)
cbData = LEN(StrPfx)
pbData = HeapAlloc(GetProcessHeap(), 0, cbData)
RtlMoveMemory(pbData, @StrPfx, cbData)
pPFX = 0h + BINTOC(cbData,"4RS") + BINTOC(pbData, "4RS")
*----- Verificamos que el archivo sea certificado Pfx, P12
IF PFXIsPFXBlob(pPFX)=0
MESSAGEBOX( "El archivo no es un PFX/P12 válido", 16, _SCREEN.Caption)
HeapFree(GetProcessHeap(), 0, pbData)
RETURN .F.
ENDIF
*----- Capturamos el Password
cPassword = STRCONV(tcPassword,5) + CHR(0)
*----- Verificamos el Password
IF PFXVerifyPassword(pPFX, cPassword, 0)=0
MESSAGEBOX("Contraseña incorrecta", 16, _SCREEN.Caption)
HeapFree(GetProcessHeap(), 0, pbData)
RETURN 0
ENDIF
*----- Importamos el o los certificados (pueden venir varios epaquetados en un P12 o PFX)
hStoreHandle = 0
hStoreHandle = PFXImportCertStore(pPFX, cPassword, CRYPT_EXPORTABLE)
cPassword = ""
pPFX = ""
HeapFree(GetProcessHeap(), 0, pbData)
IF hStoreHandle=0
THIS.GetMessageError()
RETURN 0
ENDIF
RETURN hStoreHandle
ENDPROC

Esteban H.

unread,
Feb 19, 2019, 3:05:57 PM2/19/19
to publice...@googlegroups.com

Felicitaciones p Diego y tb p vos Fernando, q diste el puntapié inicial p poder lograr esto en VFP.

Yo actualmente firmo el xml con OpenSSL, lo cierto es q VFP no necesita q el certificado sea en formato PFX/P12, con el OpenSSL y el archivo crt + la Key es suficiente p hacer el firmado. ( AFIP Argentina )

Donde disponga de + tiempo, voy a mirar bien lo q Diego ha logrado con la modificación de tu código inicial.

 

Saludos

 

Esteban

Fernando Mora

unread,
Feb 19, 2019, 3:10:26 PM2/19/19
to Comunidad de Visual Foxpro en Español
Estoy en la construcción de una librería que permita trabajar con certificados desde CertStore, archivo Pfx/p12, o Archivos Pem, Key proveniente de otros proveedores criptograficos, Ya puedo importar archivos Pem, Cer, etc. donde estoy atascando es en la conversión del par de claves (archivos Key), según leo para esto hay que imprtar el blob de datos, convertirlo a formato ASN1, y luego Decodificarlo con CryptDecodeObjectEx, con la constante PKCS_RSA_PRIVATE_KEY, que tiene un valor de (LPCSTR) 43, he probado de varias formas pasar ese dato a la función, pero no hallo manera de que la reconozca. he Intentando, STRCONV("43",5) + CHR(0), sin CHR(0), sin STRCONV, etc. pero no reconoce la constante. Paso el codigo de la función para que todos la revise, y vean como podemos sortear este obstáculo

PROCEDURE
GetImportPrivateKeyToCng
PARAMETERS tcFileKey AS String
SET STEP ON
cStringKey = ALLTRIM(FILETOSTR(tcFileKey))
IF EMPTY(cStringKey)
MESSAGEBOX("El archivo esta vacio", 16, _SCREEN.Caption)
RETURN 0
ENDIF 
*------- Decodificamos el archivo 
dwBufferLen=0
pdwSkip=0
pdwFlags=0
nResp = CryptStringToBinary(cStringKey, 0, CRYPT_STRING_ANY, NULL, @dwBufferLen, @pdwSkip, @pdwFlags)
DO CASE
CASE pdwFlags=0
*------- Base64, with certificate beginning and ending headers.
lcPrivKey=STREXTRACT(cStringKey, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----")
IF EMPTY(lcPrivKey)
lcPrivKey=STREXTRACT(cStringKey, "-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----END ENCRYPTED PRIVATE KEY-----")
ENDIF
lcPrivKey=STRCONV(lcPrivKey,14)
CASE pdwFlags=1
*------- Base64, without headers.
lcPrivKey=STRCONV(cStringKey,14)
CASE pdwFlags=2
*------- Pure binary copy.
lcPrivKey=cStringKey
ENDCASE
*------- Decodificamos la cadena ASN1
dwCertEncodingType = BITOR(PKCS_7_ASN_ENCODING, X509_ASN_ENCODING)
lpszStructType = "43" + CHR(0)
pbEncoded = lcPrivKey
cbEncoded = LEN(lcPrivKey)
cbKeyBlob = 0
nResp=CryptDecodeObjectEx(dwCertEncodingType, lpszStructType, pbEncoded, cbEncoded, 0, NULL, NULL, @cbKeyBlob)
IF cbKeyBlob=0
THIS.GetMessageerror()
RETURN 0
ENDIF
pbKeyBlob = SPACE(pbKeyBlob)
nResp=CryptDecodeObjectEx(dwCertEncodingType, lpszStructType, pbEncoded, cbEncoded, 0, NULL, @pbKeyBlob, @cbKeyBlob)

*------- Adqurimos un proveedor criptografico
hProv=0
nResp=CryptAcquireContext(@hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)

hKey=0
CryptImportKey(hProv, lcPrivKey, cbEncoded, 0, 0, @hKey)

*------- Abrimos un proveedor de algoritmo
pszAlgId = STRCONV("RSA" + CHR(0), 5)
lnAlg = 0
lnRes = BCryptOpenAlgorithmProvider(@lnAlg, pszAlgId, NULL, 0)
IF lnRes <> 0
MESSAGEBOX("No se pudo apertura el proveedor de algoritmo", 16, _SCREEN.Caption)
RETURN 0
ENDIF
*-------- Importamos el par de claves, probamos con varios formatos
pszBlobType01 = STRCONV("PRIVATEBLOB" + CHR(0), 5)
pszBlobType02 = STRCONV("CAPIPRIVATEBLOB" + CHR(0), 5)
pszBlobType03 = STRCONV("DHPRIVATEBLOB" + CHR(0), 5)
pszBlobType04 = STRCONV("DSAPRIVATEBLOB" + CHR(0), 5)
pszBlobType05 = STRCONV("ECCPRIVATEBLOB" + CHR(0), 5)
pszBlobType06 = STRCONV("RSAPRIVATEBLOB" + CHR(0), 5)
pszBlobType07 = STRCONV("CAPIDHPRIVATEBLOB" + CHR(0), 5)
pszBlobType08 = STRCONV("CAPIDSAPRIVATEBLOB" + CHR(0), 5)
pszBlobType09 = STRCONV("V2CAPIDSAPRIVATEBLOB" + CHR(0), 5)
lnKey = 0
DO CASE
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType01, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType02, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType03, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType04, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType05, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType06, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType07, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType08, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
CASE BCryptImportKeyPair(lnAlg, 0, pszBlobType09, @lnKey, lcPrivKey, LEN(lcPrivKey), 0)==0
OTHERWISE
MESSAGEBOX("No se pudo importar el par de claves."+CHR(13)+"revise que el archivo key tenga el formato correcto", 16, _SCREEN.Caption)
ENDCASE
BCryptCloseAlgorithmProvider(lnAlg, 0)
RETURN lnKey

ENDPROC


.  

Diego

unread,
Feb 19, 2019, 3:17:48 PM2/19/19
to publice...@googlegroups.com
Fernando, a ver si te entendi bien. Estas atascado en la conversión del par de claves? Que seria esto especificamente?

Diego

Fernando Mora

unread,
Feb 19, 2019, 3:56:15 PM2/19/19
to Comunidad de Visual Foxpro en Español
Si, quiero que la librería tenga la capacidad de importar archivos PEM y KEY que genera Openssl o cualquier otro provedor criptografico. No solo permitir el firmado desde CertStore y archivos PFX/P12, sino también la importación de archivos generados por otros proveedores criptograficos. En teoría se puede hacer, supuestamente para eso se debe de decodificar con CryptDecodeObjectEx, el problema es el segundo parámetro, lpszStructType debería ser una cadena (LPCSTR) 43 en C++, según la doc. de MS el formato LPCSTR es un puntero de 32 bits a una cadena constante terminada en nulo de caracteres de Windows de 8 bits ( ANSI ). En Fox sería un simple "43"+CHR(0), pero CryptDecodeObjectEx no reconoce como valido ese valor.

Fernando Mora

unread,
Feb 19, 2019, 4:09:20 PM2/19/19
to Comunidad de Visual Foxpro en Español
Este es mi código para importar archivos CER o PEM. La función devuelve el contexto del certificado, desde ahí ya puedes trabajarlo para realizar un firmado, o importarlo definitivamente a un almacén ("MY") de certificados del sistema con CertAddCertificateContextToStore


PROCEDURE GetImportCert(tcFilePemOrCer AS String)
cStringCert=FILETOSTR(tcFilePemOrCer)
IF EMPTY(cStringCert)
MESSAGEBOX("El archivo esta vacio", 16, _SCREEN.Caption)
RETURN 0
ENDIF
dwCertEncodingType = BITOR(PKCS_7_ASN_ENCODING, X509_ASN_ENCODING)
pCertContext = 0
IF "BEGIN CERTIFICATE"$cStringCert
pbCertEncoded=STREXTRACT(cStringCert, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----")
IF EMPTY(pbCertEncoded)
MESSAGEBOX("No se detecto datos en el certificado", 16, _SCREEN.Caption)
RETURN 0
ENDIF
pbCertEncoded = STRCONV(ALLTRIM(STRTRAN(pbCertEncoded, CHR(0), "")),14)
cbCertEncoded = LEN(pbCertEncoded)
pCertContext = CertCreateCertificateContext(dwCertEncodingType, pbCertEncoded, cbCertEncoded)
ELSE
pbCertEncoded = cStringCert
cbCertEncoded = LEN(pbCertEncoded)
pCertContext = CertCreateCertificateContext(dwCertEncodingType, pbCertEncoded, cbCertEncoded)
IF pCertContext=0
pbCertEncoded = STRCONV(cStringCert,14)
cbCertEncoded = LEN(pbCertEncoded)
pCertContext = CertCreateCertificateContext(dwCertEncodingType, pbCertEncoded, cbCertEncoded)
ENDIF
ENDIF
IF pCertContext=0
MESSAGEBOX("No se pudo importar el certificado", 16, _SCREEN.Caption)
ENDIF
RETURN pCertContext
ENDPROC


Diego

unread,
Feb 19, 2019, 4:41:35 PM2/19/19
to publice...@googlegroups.com
Ahh ok. Entendi. Me queres pasar el certificado con el que estas haciendo la prueba asi seguimos la misma linea?
Otra cosa. Lo que no veo en tu codigo es dnd abre el KEY.

Diego

Fernando Mora

unread,
Feb 19, 2019, 8:48:10 PM2/19/19
to Comunidad de Visual Foxpro en Español
Listo Diego, aquí arme un programa con la parte que se necesita resolver, y anexo un archivo KEY, que contiene el par de claves privada/publica. Aquí aprendemos algo respecto a reconocer si una cadena esta o no esta en base64, la función CryptStringToBinari nos devuelve la respuesta en su ultimo parámetro pdwFlags pasandole la constantes CRYPT_STRING_ANY (valor 7) en su tercer parámetro, si pdwFlags retorna es 0, la cadena esta en formato b64 con encabezados, si es 1, esta en b64 sin encabezados, si es 2 esta en Binarios ASN1, de esta manera sabremos si hay que convertir o no la cadena. 


*------------ CRYPTO API CSP (Cryptographic Service Providers)
*------------ Crypt32.DLL
DECLARE LONG CertCreateCertificateContext IN Crypt32;
LONG dwCertEncodingType,;
STRING pbCertEncoded,;
LONG cbCertEncoded

DECLARE LONG CryptBinaryToString IN Crypt32;
STRING pbBinary, ;
LONG cbBinary, ;
LONG dwFlags,;
STRING @pszString, ;
LONG @pcchString

DECLARE LONG CryptStringToBinary IN Crypt32;
STRING @pszString, ;
LONG cchString, ;
LONG dwFlags,;
STRING @pbBinary, ;
LONG @pcbBinary,;
LONG @pdwSkip, ;
LONG @pdwFlags

DECLARE LONG CryptEncodeObject IN Crypt32;
LONG dwCertEncodingType,;
STRING lpszStructType,;
STRING pvStructInfo,;
STRING @pbEncoded,;
LONG @pcbEncoded

DECLARE LONG CryptEncodeObjectEx IN Crypt32;
LONG dwCertEncodingType,;
STRING lpszStructType,;
STRING pvStructInfo,;
LONG dwFlags,;
STRING pEncodePara,;
STRING @pvEncoded,;
LONG @pcbEncoded

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

DECLARE LONG CryptDecodeObjectEx IN Crypt32;
LONG dwCertEncodingType,;
STRING lpszStructType,;
STRING pbEncoded,;
LONG cbEncoded,;
LONG dwFlags,;
STRING pDecodePara,;
STRING @pvStructInfo,;
LONG @pcbStructInfo

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

*------- CRYPTO API CNG (Cryptography Next Generation)
*------- BCrypt.DLL
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 BCryptCloseAlgorithmProvider IN BCrypt; 
LONG hAlgorithm, ;
LONG dwFlags 

DECLARE LONG BCryptDestroyKey IN BCrypt; 
LONG hKey 

*------------ Kernel32.DLL
DECLARE LONG GetLastError IN Kernel32

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

*------------ Constantes
#DEFINE X509_ASN_ENCODING 1  
#DEFINE PKCS_7_ASN_ENCODING 0x00010000
#DEFINE FORMAT_MESSAGE_FROM_SYSTEM 0x00001000
#DEFINE CRYPT_STRING_ANY 0x00000007

*------------ Main
lcFileKey = GETFILE("KEY")
IF EMPTY(lcFileKey)
RETURN 0
ENDIF
lpParKey = GetImportPrivateKeyToCng(lcFileKey)
IF lpParKey<>0
? "Exito!, puntero: " + TRANSFORM(lpParKey)
BCryptDestroyKey(lpParKey)
ELSE
? "No se pudo importar el par de claves"
ENDIF

*------------ Región de procedures
*/// Recibe la ruta de un archivo de intercambio de claves publico/privada (KEY)
*/// Si tiene éxito devuelve un puntero al par de claves importadas, listo para firmar
PROCEDURE GetImportPrivateKeyToCng(tcFileKey AS String)
SET STEP ON 
cStringKey = ALLTRIM(FILETOSTR(tcFileKey))
IF EMPTY(cStringKey)
MESSAGEBOX("El archivo esta vacio", 16, _SCREEN.Caption)
RETURN 0
ENDIF 
*----- Decodificamos el archivo 
dwBufferLen=0
pdwSkip=0
pdwFlags=0
nResp = CryptStringToBinary(cStringKey, 0, CRYPT_STRING_ANY, NULL, @dwBufferLen, @pdwSkip, @pdwFlags)
DO CASE
CASE pdwFlags=0
*----- Archivo Base64, con encabezados de inicio y fin, convertir a ASN1
lcPrivKey=STREXTRACT(cStringKey, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----")
IF EMPTY(lcPrivKey)
lcPrivKey=STREXTRACT(cStringKey, "-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----END ENCRYPTED PRIVATE KEY-----")
ENDIF
lcPrivKey=STRCONV(lcPrivKey,14)
CASE pdwFlags=1
*----- Archivo Base64, sin encabezados, convertir a ASN1
lcPrivKey=STRCONV(cStringKey,14)
CASE pdwFlags=2
*------- Archivo ya en formato binario ASN1, NO convertir
lcPrivKey=cStringKey
ENDCASE
*----- Decodificamos la cadena ASN1
dwCertEncodingType = BITOR(PKCS_7_ASN_ENCODING, X509_ASN_ENCODING)
lpszStructType = "43" + CHR(0) && AQUI EL PROBLEMA, A ESTE PARAMETRO HAY QUE ENTRARLE A COMO SEA!
pbEncoded = lcPrivKey
cbEncoded = LEN(lcPrivKey)
cbKeyBlob = 0
nResp=CryptDecodeObjectEx(dwCertEncodingType, lpszStructType, pbEncoded, cbEncoded, 0, NULL, NULL, @cbKeyBlob)
IF cbKeyBlob=0
GetMessageError()
RETURN 0
ENDIF
pbKeyBlob = SPACE(pbKeyBlob)
nResp=CryptDecodeObjectEx(dwCertEncodingType, lpszStructType, pbEncoded, cbEncoded, 0, NULL, @pbKeyBlob, @cbKeyBlob)
*----- Abrimos un proveedor de algoritmo
pszAlgId = STRCONV("RSA" + CHR(0), 5)
lnAlg = 0
lnRes = BCryptOpenAlgorithmProvider(@lnAlg, pszAlgId, NULL, 0)
IF lnRes <> 0
MESSAGEBOX("No se pudo apertura el proveedor de algoritmo", 16, _SCREEN.Caption)
RETURN 0
ENDIF
*------ Importamos el par de claves para CNG, probamos con varios formatos
PROCEDURE GetMessageError(tnNumError AS Long)
IF VARTYPE(tcNumError)=="N"
lnErrorCode = tnNumError
alice.key

Diego Fazio

unread,
Feb 20, 2019, 6:46:08 AM2/20/19
to Comunidad de Visual Foxpro en Español
Fernando, para mi lpszStructType esta bien definido. No hay mucho secreto. Tal cual comentas es un LPCSTR que basicamente es un string + chr(0). 
Lo que no me cierra es el mensaje de error que tira "El sistema no puede encontrar el archivo especificado" ¿?¿?¿ Que significaria eso?

Diego.

Diego Fazio

unread,
Feb 20, 2019, 7:29:13 AM2/20/19
to Comunidad de Visual Foxpro en Español
Me respondo a mi mismo. El error en realidad es "Decoding function could not be found"

Diego.

Fernando Mora

unread,
Feb 20, 2019, 10:28:43 AM2/20/19
to Comunidad de Visual Foxpro en Español
Diego, el mismo programa lo acabo de codificar en C++ y funciona, lo que he observado es que el parámetro en cuestión, su tipo LPCSTR luce como una matriz de char que tiene  3 elementos 4, 3, y 0 que representa null, se me ocurre crear un vector, rellenar con esos valores, meterlo a un puntero de memoria y pasarlo, a ver que pasa. Mira si lo intentas, ahora me toco viajar a visitar un par de clientes. Hay que ver como entrarle a ese parámetro, Yo he usado la función CryptDecodeObjectEx para decodificar extensiones y funciona bien, cuando el parámetro es por ejemplo "2.5.29.32", devuelve datos sin problemas, lo que da problema es cuando es de tipo LPCSTR, ahí se nos viene abajo todo.....


Diego Fazio

unread,
Feb 20, 2019, 11:57:43 AM2/20/19
to Comunidad de Visual Foxpro en Español
Fernando, pasame el codigo en C++ que te funciona bien al que le estas pasando (LPCSTR) 43

Diego.

Fernando Mora

unread,
Feb 21, 2019, 12:34:31 AM2/21/19
to Comunidad de Visual Foxpro en Español
Aquí te arme un fragmento de mi cogido de C++, coloque los bytes de un Key en una constante para simplificar el ejemplo. La clase devuelve un puntero para Cng de un par de claves importadas. Con eso ya se podría firmar un Xml, esto hay que replicarlo en Fox, pero aun no hallo la forma, creo habrá que ver otra alternativa a CryptDecodeObjectEx. Por ahora mi librería de fox firma con certificados instalados en CertSotre, tambien con certificados en archivos pfx/p12, se puede importar certificados desde formato Pem o Cer, solo falta importar el par de claves.... logramos eso y ufff,  esa librería queda 100%. Lista para cualquier combate. Firma tanto en CNG como en CSP. 


#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <wincrypt.h>

#pragma comment (lib, "crypt32.lib" )
#pragma comment (lib, "bcrypt.lib" )

const char* szPemPrivKey =
"-----BEGIN RSA PRIVATE KEY-----"
"MIICXAIBAAKBgQCf6YAJOSBYPve1jpYDzq+w++8YVoATI/YCi/RKZaQk+l2ZfoUQ"
"g0qrYrfkzeoOa/qd5VLjTTvHEgwXnlDXMfo+vSgxosUxDOZXMTBqJGOViv5K2QBv"
"k8A1wi4k8tuo/7OWya29HvcfavUk3YXaV2YFe8V6ssaZjNcVWmDdjqNkXwIDAQAB"
"AoGALrd+ijNAOcebglT3ioE1XpUbUpbir7TPyAqvAZUUESF7er41jY9tnwgmBRgL"
"Cs+M1dgLERCdKBkjozrDDzswifFQmq6PrmYrBkFFqCoLJwepSYdWnK1gbZ/d43rR"
"2sXzSGZngscx0CxO7KZ7xUkwENGd3+lKXV7J6/vgzJ4XnkECQQDTP6zWKT7YDckk"
"We04hbhHyBuNOW068NgUUvoZdBewerR74MJx6nz28Tp+DeNvc0EveiQxsEnbV8u+"
"NRkX5y0xAkEAwcnEAGBn5kJd6SpU0ALA9XEpUv7tHTAGQYgCRbfTT59hhOq6I22A"
"ivjOCNG9c6E7EB2kcPVGuCpYUhy7XBIGjwJAK5lavKCqncDKoLwGn8HJdNcyCIWv"
"q5iFoDw37gTt1ricg2yx9PzmabkDz3xiUmBBNeFJkw/FToXiQRGIakyGIQJAJIem"
"PPPvYgZssYFbT4LVYO8d/Rk1FWVyKHQ9CWtnmADRXz7oK7l+m7PfEuaGsf9YpOcR"
"koGJ/TluQLxNzUNQnQJBAImwr/yYFenIx3HQ6UX/fCt6qpGDv0VfOLyR64MNeegx"
"o7DhNxHbFkIGzk4lKhMKcHKDrawZbdJtS9ie2geSwVQ="
"-----END RSA PRIVATE KEY-----";

BCRYPT_KEY_HANDLE main(int argc)
{
DWORD dwBufferLen = 0, cbKeyBlob = 0, cbSignature = 0, i;
LPBYTE pbBuffer = NULL, pbKeyBlob = NULL, pbSignature = NULL;
HCRYPTPROV hProv = NULL;
HCRYPTKEY hKey = NULL;
HCRYPTHASH hHash = NULL;
LPCSTR cadfer = ((LPCSTR) 43);

CryptStringToBinaryA(szPemPrivKey, 0, CRYPT_STRING_BASE64HEADER, NULL, &dwBufferLen, NULL, NULL);
pbBuffer = (LPBYTE)LocalAlloc(0, dwBufferLen);
CryptStringToBinaryA(szPemPrivKey, 0, CRYPT_STRING_BASE64HEADER, pbBuffer, &dwBufferLen, NULL, NULL);

CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, pbBuffer, dwBufferLen, 0, NULL, NULL, &cbKeyBlob);
pbKeyBlob = (LPBYTE)LocalAlloc(0, cbKeyBlob);
CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, pbBuffer, dwBufferLen, 0, NULL, pbKeyBlob, &cbKeyBlob);

printf("Esto son los bytes del par de claves importados %s \n", pbBuffer);
BCRYPT_ALG_HANDLE lnAlg = 0;
BCryptOpenAlgorithmProvider(&lnAlg, L"RSA", NULL, 0);

BCRYPT_KEY_HANDLE phKey = 0;
BCryptImportKeyPair(lnAlg, 0,  L"PRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "PRIVATEBLOB!\n"; return phKey;}
BCryptImportKeyPair(lnAlg, 0, L"CAPIPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "CAPIPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"DHPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "DHPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"DSAPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "DSAPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"ECCPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "ECCPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"RSAPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "RSAPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"CAPIDHPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "CAPIDHPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"CAPIDSAPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "CAPIDSAPRIVATEBLOB!\n"; return phKey;}

BCryptImportKeyPair(lnAlg, 0, L"V2CAPIDSAPRIVATEBLOB", &phKey, pbKeyBlob, cbKeyBlob, 0);
if (phKey != 0) {std::cout << "V2CAPIDSAPRIVATEBLOB!\n"; return phKey;}

}


Diego Fazio

unread,
Feb 21, 2019, 5:14:23 AM2/21/19
to Comunidad de Visual Foxpro en Español
Ese codigo es el que encontre como ejemplo en la web por eso es que queria ver tu codigo origen en C y por lo que vi se diferencia al de VFP en que ahi la conversion la hace con el mismo comando CryptStringToBinaryA, no con STRCONV. No estara ahi el problema? Yo sigo pensando que lpszStructType no es el problema.-
Probaste hacerlo paso a paso tal cual este ejemplo que me pasaste pero en VFP?

Diego.

Fernando Mora

unread,
Feb 21, 2019, 8:52:15 AM2/21/19
to Comunidad de Visual Foxpro en Español
Diego en lugar lpszStructType = '43' + CHR(0), prueba lpszStructType ='"2.5.29.14", observa el error que arroja ahí, ese mensaje es el que corresponde cuando la cadena ASN1 no es la esperada, muy diferente al mensaje que arroja cuando el parámetro no logra ser identificado y la api no logra encontrar la decodificación correcta. Entonces en C++ no se estanca en el parámetro, si en C++ cambias el valor del archivo key y la cadena ASN1 no es la esperada, te arrojara el mismo errar que en Fox. has la prueba. 

Diego Fazio

unread,
Feb 21, 2019, 9:14:07 AM2/21/19
to Comunidad de Visual Foxpro en Español
Sisi, ayer cuando me comentaste, probe con "2.5.29.14" y tambien observe que el error que tira es otro.

Sigo mirando....

Diego 

Fernando Mora

unread,
Feb 21, 2019, 9:23:21 AM2/21/19
to Comunidad de Visual Foxpro en Español
Diego, revisa este articulo, mas o menos dice que conversión se necesita hacer a la cadena para que CryptoApi la acepte como pares de clave valido y poder importarla. Si no logramos encontrar la forma de pasarle el parámetro correcto a CryptDecodeObjectEx, creo nos va a tocar escribir nuestra propia función.... ufff

Diego Fazio

unread,
Feb 21, 2019, 1:25:04 PM2/21/19
to Comunidad de Visual Foxpro en Español
Ferando, tenias razon. El problema al final era en lpszStructType. El tema estaba aca....

Mira la documentacion del CryptDecodeObjectEx....


lpszStructType: 

A pointer to an object identifier (OID) that defines the structure type. If the high-order word of the lpszStructType parameter is zero, the low-order word specifies the integer identifier for the type of the specified structure. Otherwise, this parameter is a long pointer to a null-terminated string.


Hay que declarar en la funcion CryptDecodeObjectEx a lpszStructType como LONG


DECLARE LONG CryptDecodeObjectEx IN Crypt32;

LONG dwCertEncodingType,;

LONG lpszStructType,;

STRING pbEncoded,;

LONG cbEncoded,;

LONG dwFlags,;

STRING pDecodePara,;

STRING @pvStructInfo,;

LONG @pcbStructInfo


Le asignas este valor....


lpszStructType = 43


Y lo pones asi en la funcion.....


nResp=CryptDecodeObjectEx(dwCertEncodingType, @lpszStructType, pbEncoded, cbEncoded, 0 , NULL, NULL, @cbKeyBlob)


FUNCIONA PERFECTO!!!

Diego.

Fernando Mora

unread,
Feb 22, 2019, 12:06:46 PM2/22/19
to Comunidad de Visual Foxpro en Español
EXCELENTE! Diego, este era un gran obstáculo para la codificación y decodificación de objetos, los parámetros que son cadena string son un poco mas de 40, pero lo que usan LPCSTR son mas de 80. Aún no logro entender como esta función admite dos formatos distintos de valores, ¿como la debemos declarar? ¿será que la llamamos con Long o String según como la necesitemos y solo para ese momento y luego la liberamos? Otra cosa, estoy usando STRCONV() para transformar a b64 porque CryptBinaryToString no detecta bien la longitud de la cadena.... creo que la cosa va porque existen dos funciones CryptBinaryToStringA y CryptBinaryToStringW, supongo que una es para este tipo de conversiones, aun no he probado pero sospecho que así es. 

Diego Fazio

unread,
Feb 22, 2019, 12:24:57 PM2/22/19
to Comunidad de Visual Foxpro en Español
Yo la llamaria con Long o String dependiento lo que necesite hacer para safar. Despues eso una vez teniendo lo demas ya cocinado se ve bien como hacer. Creo que va a haber que manejarlo todo como string calculo.
Con respecto a lo otro, yo arme el codigo ultimo en C++ que me pasaste pero en VFP y funciono bien. Usando CryptBinaryToStringA, primero para detectar el tamaño y luego para hacer la transformacion y creo que funciono bien.

Diego.

Fernando Mora

unread,
Feb 22, 2019, 1:20:52 PM2/22/19
to Comunidad de Visual Foxpro en Español
Diego probaste con varios keys? yo probé con varios (RSA, DH, CAPI, DSA) con cabecera, sin cabecera, en formato en b64 y ASN1, CryptStringToBinary fallo en algunos. STRCONV no. 

Diego Fazio

unread,
Feb 22, 2019, 1:27:23 PM2/22/19
to Comunidad de Visual Foxpro en Español
Te paso la parte del codigo mas importante. Si encontras algun ejemplo con esto que falle, pasame el key y veo que onda.

Diego.


DECLARE LONG CryptDecodeObjectEx IN Crypt32;
LONG dwCertEncodingType,;
LONG lpszStructType,;
STRING pbEncoded,;
LONG cbEncoded,;
LONG dwFlags,;
STRING pDecodePara,;
STRING @pvStructInfo,;
LONG @pcbStructInfo

DECLARE INTEGER CryptStringToBinaryA IN Crypt32;
STRING @pszString, ;
LONG cchString, ;
LONG dwFlags,;
STRING @pbBinary, ;
LONG @pcbBinary,;
LONG pdwSkip, ;
LONG pdwFlags


PROCEDURE GetImportPrivateKeyToCng(tcFileKey AS String)
*!* SET STEP ON 
clear
cStringKey = ALLTRIM(FILETOSTR(tcFileKey))
IF EMPTY(cStringKey)
MESSAGEBOX("El archivo esta vacio", 16, _SCREEN.Caption)
RETURN 0
ENDIF 
*----- Decodificamos el archivo 
pcbBinary=0
pdwSkip=0
pdwFlags=0
nResp = CryptStringToBinaryA(@cStringKey, LEN(cStringKey), CRYPT_STRING_BASE64HEADER, NULL, @pcbBinary, @pdwSkip, @pdwFlags)
pbBinary  = REPLICATE(CHR(0), pcbBinary)
nResp = CryptStringToBinaryA(@cStringKey,LEN(cStringKey),CRYPT_STRING_BASE64HEADER,@pbBinary,@pcbBinary,@pdwSkip,@pdwFlags)
*----- Decodificamos la cadena ASN1
dwCertEncodingType = BITOR(PKCS_7_ASN_ENCODING, X509_ASN_ENCODING)
lpszStructType = 43
pbEncoded = pbBinary
cbEncoded = pcbBinary
pbKeyBlob = 0
cbKeyBlob = 0
nResp=CryptDecodeObjectEx(dwCertEncodingType, @lpszStructType, pbEncoded, cbEncoded, 0 , NULL, NULL, @cbKeyBlob)

etc etc...

Diego.

Fernando Mora

unread,
Feb 22, 2019, 2:51:21 PM2/22/19
to Comunidad de Visual Foxpro en Español
Diego, al pasar la constante CRYPT_STRING_BASE64HEADER, estas asumiendo que el archivo key esta en b64 y con encabezados, pero que tal si el usuario intenta usar un archivo sin encabezado, o peor que el archivo no este en b64. La constante CRYPT_STRING_ANY te devuelve en que formato esta el archivo en el ultimo parámetro (pdwFlags). 0 si esta en b64 con encabezados, 1 si esta en b64 sin encabezados, y 2 si esta en otro formato, podría ser ya ASN1. Prueba con el certificado que te pase (alice). Ese no esta en b64, 

Diego

unread,
Feb 22, 2019, 3:04:30 PM2/22/19
to publice...@googlegroups.com
Sisi, eso sabia. A lo que me referia es respecto que me comentaste que la función CryptBinaryToStringA no te detecta bien la longitud de la cadena. Y fallo en algunos casos. Tenes un ej de esto?

Diego

Enviado desde mi iPhone

Fernando Mora

unread,
Feb 22, 2019, 3:13:25 PM2/22/19
to Comunidad de Visual Foxpro en Español
Si, intenta con el key de Alice, ese esta en ASN1,  CryptBinaryToStringA con CRYPT_STRING_BASE64HEADER te devuelve longitud CERO y esta correcto porque no esta en B64, y con la constante CRYPT_STRING_ANY, logra devolver bien en pdwFlas 2, pero no detecta bien su longitud, devuelve 33. 

Diego Fazio

unread,
Feb 25, 2019, 6:16:21 AM2/25/19
to Comunidad de Visual Foxpro en Español
Fernando, buen dia. Por lo que vi la longitud la esta detectando bien. Lo que no esta haciendo es el CryptDecodeObjectEx. Devuelve el error "Se ha encontrado un valor de etiqueta ASN1....:" 
Me podras decir el ALICE.KEY como lo generaste o lo obtuviste?

Diego.

Fernando Mora

unread,
Feb 25, 2019, 10:12:28 AM2/25/19
to Comunidad de Visual Foxpro en Español

net_bxrypt.jpg

Hola Diego, ese certificado esta colgado en la pagina web que publique en temas mas arriba (https://www.di-mgt.com.au/xmldsig2.html) donde explican la canonicalización, y ya me di cuenta que el par de claves esta protegido con contraseña (la contraseña es "password") supongo que hay que usar las apis para desencriptar el archivo primero,  Justo voy por ahí, estoy revisando encriptaciones y desencriptaciones con CryptoApi, en este momento me urge el formato de encriptado TripleDes, que usamos acá para unos reportes de la UAFE, Eso lo tengo hecho en Java y C#. donde vienen clases ya hechas, solo se le pasa los valores y te devuelve el dato encriptado. C# usa CryptoApi, estoy tratando de seguirle la pista a las fuenste de C# por lo pronto observo que usan algunas funciones de BCrypt entre ellas BCryptOpenAlgorithmProvider, BCryptImportKey, BCryptEncrypt, entre otras., adjunto imagen. 

Ever Rios

unread,
Feb 25, 2019, 11:57:45 AM2/25/19
to publice...@googlegroups.com
Hola Fernando.

Te agradecería que me  puedas enviar la documentación; excelente iniciativa.

Saludos.

Ever

El mar., 8 ene. 2019 a las 10:16, Fernando Mora (<servipcco...@gmail.com>) escribió:
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




Diego Fazio

unread,
Feb 25, 2019, 12:44:03 PM2/25/19
to Comunidad de Visual Foxpro en Español
Fernando. Me parecia que algo faltaba ya que abri diferentes tipos de KEY sin problema con la ultima rutina que te pase. Esta entonces como me comentas hay que desencriptarla con la clave "password" y luego hacerla correr normalmente como hice con las demas.
Seria asi?

Diego.

Fernando Mora

unread,
Feb 25, 2019, 5:19:44 PM2/25/19
to Comunidad de Visual Foxpro en Español
Así parecer ser Diego, revisa las funciones BCryptOpenAlgorithmProvider, BCryptGenerateSymmetricKey, BCryptGetProperty, BCryptDecrypt, BCryptEncrypt creo que por ahí es el camino, estoy revisando esas funciones y tratando de hacer encriptaciones y desentriptaciones, pero aun no he logrado nada.....

Fernando Mora

unread,
Feb 26, 2019, 12:44:30 PM2/26/19
to Comunidad de Visual Foxpro en Español
Hola Diego, ya logre que las funciones de cifrado y descifrado con TriplDes den respuestas, cifra y descifra los datos, pero las respuestas no son las mismas que me arroja Java o Net, al parecer creo hay que aplicar alguna conversión al dato que se desea cifras, pero no se cual sea. Te paso el código a tu Correo para que le eche una revisada? confirma. 

Diego

unread,
Feb 26, 2019, 1:04:16 PM2/26/19
to publice...@googlegroups.com
Dale

Diego

pablo....@kmpesoluciones.com

unread,
Mar 25, 2019, 9:35:15 AM3/25/19
to Comunidad de Visual Foxpro en Español
Fernando buenos días, he revisado tu publicación y me parece algo genial hacer todo desde VFP, talvez me puedes ayudar con más información, te lo agradecería muchísimo, mi correo es pablo....@outlook.com

Saludos y muchas gracias por tan enorme aporte

Jose Casalino

unread,
Mar 26, 2019, 11:04:28 AM3/26/19
to Comunidad de Visual Foxpro en Español
Hola Fernando Mora

Estoy muy interesado en lo que publicaste. Lamentablemente me perdi al leer tanto jejeje.

No se si puedas ayudarme mandandome la informacion. yo tambien estoy en Ecuador y me han pedido pasar un siststema a facturacion electronica donde trabajo y ya llegue hasta crear el xml .

Mi correo es joseca...@hotmail.com 

Muchas Gracias  de antemano


El lunes, 4 de febrero de 2019, 11:25:54 (UTC-5), Fernando Mora escribió:

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,
Apr 3, 2019, 1:52:49 AM4/3/19
to Comunidad de Visual Foxpro en Español
Saludos Amigos.

Jose Casalino, compa si ya puedes hacer los xml desde fox ya tienes casi todo hecho,  revisa como esta conformado el Nodo Signature (<ds:Signature>) son cuatro subnodos, SignedInfo, SignatureValue, KeyInfo y Object. Donde el nodo SignedInfo contienen los Hash (DigestValue) de las regiones del Xml al que se desea aplicar seguridad de integridad. Esos Hash los vas obteniendo con la función DigestValue del código que publique, con eso vas armando el nodo SignedInfo, cuando ya tengas los tres hash, creas un cuarto hash con toda el texto del nodo SignedInfo, ese Hash no se muestra, ese es el hash que se firma, el resultado de firmar ese Hash lo colocas en el subnodo SignatureValue. El subnodo KeyInfo contienen el Certificado codificado , el Modulus y el Exponente del certificado. y por ultimo el nodo Object contienen las propiedades del certificado que son el numero de serie, el IssuerName (emisor del certificado), fecha y hora de firmado (SigningTime) y un Hash del certificado codificado. Para obtener esos datos del certificado se necesita decodificar el certificado, Revisa que también publique en este hilo sobre como se hace eso. 
Desde febrero empece a utilizar el firmado 100% FoxPro y los resultados son excelentes, me he topado con varios casos, y se han ido resolviendo, ya he trabajado con todos los certificados, tanto en archivo como en token USB, Los certificados del Banco Central y los Security Data funcionan 100% con CNG, los certificados del Consejo de la Judicatura, los de archivo funcionan con CNG, los de token USB esos NO FUNCIONAN con CNG, funcionan con CSP la librería antigua de CryptoAPI, OJO CON ESO, el proceso en CSP se usa las funciones de Advapi32.Dll son diferentes a CNG y los resultados del firmado están inversos, es decir la cadena se la debe invertir antes de pasar a base64, Darme cuenta de eso me tomo un chorro de días. Pilas con eso, si necesitas ayuda en el proceso de armar tu propia librería, me avisas, porque parte vas, en que parte necesitas ayuda. 
Saludos desde Machala.

Fernando.
It is loading more messages.
0 new messages