Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Proyecto SIF/Veri*Factu/Ley Antifraude > Envío de registros y sus respuestas
Registrarse FAQ Miembros Calendario Guía de estilo Temas de Hoy

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 14-11-2024
austral_es austral_es is offline
Miembro
 
Registrado: may 2017
Posts: 32
Poder: 0
austral_es Va por buen camino
Como usar un Certificado desde el almacén de Windows y asignarle una password

Estamos intentando usar un certificado instalado en el almacén de windows para realizar la remisión a VeriFactu.
Para esto usamos el evento HTTPWebNode.OnNeedClientCertificate(const Sender: TObject; const ARequest: TURLRequest; const ACertificateList: TCertificateList var AnIndex: Integer);
En este evento nos llega una lista de certificados válidos para el server de la AEAT y escogemos uno de ellos.
Ahora bien, queremos que el proceso sea automático y en caso de que el certificado use una Passowrd nos salta la pantalla de Windows pidiendo la contraseña del certificado. Queríamos evitar esto.

Se nos ocurren dos opciones:

1) Poder asignar por código la Password directamente al certificado escogido en el OnNeedCertificate pero solo podemos asignar el parámetro de salida AnIndex.

2) Sabemos que existe una forma de asignar las password de la siguiente forma:

Cita:
// Asignar el cetificado
HTTPWebNode.ClientCertificate.Stream := Certificate;
ó
HTTPWebNode.ClientCertificate.FileName := xxx

// y a continuación la password:
HTTPWebNode.ClientCertificate.Password := CertPassword;


Pero esto implica tener el certificado en un Stream o un fichero y eso es lo que queremos evitar, queremos que sea uno de los válidos que nos entrega la AEAT en el OnNeedClientCertificate.
Quizás hay alguna forma de exportar desde el Almacén de certificados de Windows hacia un Stream y así asignarlo directamente al HTTPWebNode.ClientCertificate.Stream y después la Password.


¿Alguien de aquí escoge el certificado desde el almacén o todos lo cargais desde un fichero?
Responder Con Cita
  #2  
Antiguo 14-11-2024
Avatar de bmfranky
bmfranky bmfranky is offline
Miembro
 
Registrado: may 2024
Ubicación: Gandia, Valencia
Posts: 599
Poder: 1
bmfranky Va por buen camino
Cita:
Empezado por austral_es Ver Mensaje
Estamos intentando usar un certificado instalado en el almacén de windows para realizar la remisión a VeriFactu.
Para esto usamos el evento HTTPWebNode.OnNeedClientCertificate(const Sender: TObject; const ARequest: TURLRequest; const ACertificateList: TCertificateList var AnIndex: Integer);
En este evento nos llega una lista de certificados válidos para el server de la AEAT y escogemos uno de ellos.
Ahora bien, queremos que el proceso sea automático y en caso de que el certificado use una Passowrd nos salta la pantalla de Windows pidiendo la contraseña del certificado. Queríamos evitar esto.

Se nos ocurren dos opciones:

1) Poder asignar por código la Password directamente al certificado escogido en el OnNeedCertificate pero solo podemos asignar el parámetro de salida AnIndex.

2) Sabemos que existe una forma de asignar las password de la siguiente forma:



Pero esto implica tener el certificado en un Stream o un fichero y eso es lo que queremos evitar, queremos que sea uno de los válidos que nos entrega la AEAT en el OnNeedClientCertificate.
Quizás hay alguna forma de exportar desde el Almacén de certificados de Windows hacia un Stream y así asignarlo directamente al HTTPWebNode.ClientCertificate.Stream y después la Password.


¿Alguien de aquí escoge el certificado desde el almacén o todos lo cargais desde un fichero?
Hola, yo lo cojo desde windows, pero por comodidad lo guardo en la base de datos, en mi configuracion, con otro password independiente deloriginal del certificado, en c# lo hgo asi.
Código:
       










public static X509Certificate2 ElegirCertificado()// aqui se habre el selector de certificados del sistema.
        {
            try
            {
                X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
                X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
                X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
                X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Selección de Certificados", "Seleccione un certificado de la lista para firmar la factura electrónica", X509SelectionFlag.SingleSelection);

                X509Certificate2Enumerator Certificado = scollection.GetEnumerator();
                X509Certificate2 cert = new X509Certificate2();
                if (scollection.Count > 0)
                {
                    X509Certificate2Enumerator en = scollection.GetEnumerator();
                    en.MoveNext();
                    cert = en.Current;
                    if (DateTime.Now > cert.NotAfter)
                    {
                        throw new Exception("Certificado caducado");
                    }
                    else
                    {
                        return cert;
                    }
                }

                if (fcollection.Count == 0)
                {
                    MessageBox.Show("No hay certificados electrónicos instalados en tu equipo");
                }
                else
                {
                    if (scollection.Count == 0)
                    {
                        MessageBox.Show("No has seleccionado ningún certificado");
                    }
                }
                store.Close();
                var temp = scollection.OfType<X509Certificate2>();
                var cert1 = scollection.OfType<X509Certificate2>().Where(x => x.Subject == "CN=FNMT-RCM").First(); 
                return cert;
            }
                       catch (Exception ex)
            {
                //  throw ex;
            }
            //X509Store store = new X509Store(StoreLocation.CurrentUser);
            //store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            //X509Certificate2Collection certificates = store.Certificates;
            //X509Certificate2Collection foundCertificates = certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
            //var cert = foundCertificates.OfType<X509Certificate>().Where(x => x.Subject == "CN="+Program.configTaller.NombreTaller).First();
            return null;
        }


//aqui lo exporto en forma de bytes[] con un password propio

var certificado = ConfigDB.ElegirCertificado();// 
            if(null != certificado)
            {
                string caducidad = certificado.GetExpirationDateString();
                Program.certificado = certificado.Export(X509ContentType.Cert, Program.passcertificado);
                txtCaducidad.Text = caducidad;
                ConfigDB.ActualizarCertificado(1, Program.certificado, caducidad);
            }
__________________
Uno se alegra de ser útil. (Isaac Asimov)
Responder Con Cita
  #3  
Antiguo 14-11-2024
javipes javipes is offline
Miembro
 
Registrado: jul 2007
Posts: 25
Poder: 0
javipes Va por buen camino
Yo con el tema de certificados, lo que hago es cargarlos desde el almacen de certificados sobre una lista que luego asigno a los items de un combobox para poder seleccionar el certificado que usaré en el envio:
Código Delphi [-]
function Certificados(ListaCertificados: TStringList): Integer;
var
  hStore: HCERTSTORE;
  PrevContext, CurContext: PCERT_CONTEXT;
  cbSize: DWORD;
  sNombre: String;
begin
  // Devuelve un StringList con los certificados instalados en el equipo
  ListaCertificados.Clear;

  hStore := CertOpenSystemStore(0, PChar('MY'));
  if Assigned(hStore) then
  begin
    PrevContext := nil;
    CurContext := CertEnumCertificatesInStore(hStore, PrevContext);
    while Assigned(CurContext) do
    begin
      sNombre := '';
      cbSize := CertGetNameString(CurContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
        nil, nil, 0);
      if cbSize > 0 then
      begin
        SetLength(sNombre, cbSize - 1);
        CertGetNameString(CurContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil,
          PChar(sNombre), cbSize);
        ListaCertificados.Add(sNombre);
      end;

      PrevContext := CurContext;
      CurContext := CertEnumCertificatesInStore(hStore, PrevContext);
    end;
  end;

  Result := ListaCertificados.Count;
end;


para cargar los certificados en un combo (cmbCertificados:TComboBox; //en el form )



Código Delphi [-]
var indice:integer; lisCert:TStringList;
begin
   //CargaCertificados;
   lisCert:=nil;
   indice:=DMEnvioLAF.CargaCertificados(lisCert);
   cmbcertificados.Clear;
   cmbCertificados.Items.AddStrings(lisCert);
   lisCert.free;
....


y cuando se selecciona un certificado del combo se deja en una variable infoCertificado:string; el contenido del combo, el elemento seleccionado


y es en el evento OnbeforePost del componente HTTPRIO donde asigno el certificado a partir de ese infocertificado.
Esto plantea un problema en delphi10 y en delphi11 porque la declaracion de tipos es distinta y en delphi11 se accede directamente para asignar los datos del certificado:


Código Delphi [-]
procedure TDMEnvioLAF.HTTPRIOENVHTTPWebNode1BeforePost(
  const HTTPReqResp: THTTPReqResp; Client: THTTPClient); //delphi11
//  const HTTPReqResp: THTTPReqResp; Data: Pointer);       delphi10

var  Store : IStore;
     Certs : ICertificates;
     Cert : ICertificate2;
     V : OleVariant;
{$REGION 'delphi10 var'}
//delphi10-------------------------------------------------------- ini
//     CertContext : ICertContext;
//     PCertContext : PCCERT_CONTEXT;
//     lasterror:cardinal; Buffer: array[0..255] of Char;
// const
//     INTERNET_OPTION_CLIENT_CERT_CONTEXT = 84;
//delphi10 ------------------------------------------------------- fin
{$ENDREGION 'delphi10 var'}
begin
     V := infoCertificado;

     Store := CoStore.Create;
     Store.Open(CAPICOM_CURRENT_USER_STORE, 'MY',CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED );
     Certs := Store.Certificates.Find(CAPICOM_CERTIFICATE_FIND_SUBJECT_NAME,V,False );

     if Certs.Count > 0 then
       begin
         Cert:=IInterface(Certs.Item[1]) as ICertificate2;
         {$REGION 'delphi10 code'}
//delphi10-------------------------------------------------------- ini
//         CertContext:=Cert as ICertContext;
//         CertContext.Get_CertContext(Integer(PCertContext));
//         if InternetSetOption(Data,INTERNET_OPTION_CLIENT_CERT_CONTEXT,PCertContext,Sizeof(CERT_CONTEXT)) = False then
//           begin
//            lasterror:=GetLastError;
//            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, LastError, 0, @Buffer, SizeOf(Buffer), nil);
//             showmessage('ERROR ('+inttostr(lasterror)+') asignando certificado'#13+Buffer);
//           end
//delphi10 ------------------------------------------------------- fin
         {$ENDREGION 'delphi10 code'}
//delphi11................................................................ini
        with HTTPRIOENV.HTTPWebNode.ClientCertificate do
          begin
          CertName:=V;
            SerialNum:=cert.serialnumber;
          end
//delphi11...............................................................fin
       end
     else
        showmessage('ERROR localizando certificado');
end;
no asigno la password, cojo el certificado directamente del almacen; no sé si al usarlo desde alli necesito informar la password. Pero a mi me funciona


ah, se necesita usar la libreria CAPICOM_TLB y tambien System.Net.HttpClient, System.Net.HttpClientComponent, Soap.Rio,
Soap.SOAPHTTPClient, Soap.SOAPHTTPTrans, soap.win.certhelper

puede que haya alguna de más, pero yo las tengo todas ellas puestas
Responder Con Cita
  #4  
Antiguo 14-11-2024
javipes javipes is offline
Miembro
 
Registrado: jul 2007
Posts: 25
Poder: 0
javipes Va por buen camino
añado. he nombrado a unit DMEnvioLAF, es un tdatamodule propio donde he declarado la funcion carga certificados
Responder Con Cita
  #5  
Antiguo 14-11-2024
javipes javipes is offline
Miembro
 
Registrado: jul 2007
Posts: 25
Poder: 0
javipes Va por buen camino
snif!


me he olfidado una funcion importante que es la que carga infocertificado, pues no lo hace directamente, ya que los valores hexadecimales me aparecen cruzados. Para cargar infocertificado cuando el usuario elige un certificado del combo utilizo esto:


Código Delphi [-]
procedure cmbCertificadosChange(Sender: TObject);
var certif:string; x:ansistring;
  function ReverseWord(s:ansistring):ansistring;
  var t:ansistring; l1,l2:ansichar; i:integer;
  begin
  t:='';
  for i:=length(s)div 2 downto 1 do
    begin
    l2:=s[2*i]; l1:=s[2*i-1];
    t:=t+l1+l2;
    end;
  result:=t;
  end;
begin
  certif:=cmbCertificados.Items[cmbCertificados.itemIndex];
  x:=DMEnvioLAF.SerialNum_Certificado(certif);
 infoCertificados:=ReverseWord(x);
end;


lamento la omisión
Responder Con Cita
  #6  
Antiguo 14-11-2024
javipes javipes is offline
Miembro
 
Registrado: jul 2007
Posts: 25
Poder: 0
javipes Va por buen camino
me vais a matar, hace falta la funcion para obtener el numero de serie del certificado




Código Delphi [-]
function SerialNum_Certificado(Nombre_Certificado: String): String;
var
  hStore: HCERTSTORE;
  PrevContext, CurContext: PCERT_CONTEXT;
  cbSize: DWORD;
  sNombre: String;
begin
  // Devuelve el Serial Number del certificado indicado
  Result := '';

  hStore := CertOpenSystemStore(0, PChar('MY'));
  if Assigned(hStore) then
  begin
    PrevContext := nil;
    CurContext := CertEnumCertificatesInStore(hStore, PrevContext);
    while Assigned(CurContext) do
    begin
      sNombre := '';
      cbSize := CertGetNameString(CurContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
        nil, nil, 0);
      if cbSize > 0 then
      begin
        SetLength(sNombre, cbSize - 1);
        CertGetNameString(CurContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil,
          PChar(sNombre), cbSize);
        if SameText(Nombre_Certificado, sNombre) then
        begin
          Result := GetCertSerialNumber(@CurContext^.pCertInfo^.SerialNumber);
          break;
        end;
      end;

      PrevContext := CurContext;
      CurContext := CertEnumCertificatesInStore(hStore, PrevContext);
    end;
  end;
end;


ahora ya creo que esta todo. SI echarais algo en falta me lo decis. Esto me ha funcionado en Delphi Berlin y en Delphi Alexandria
Responder Con Cita
  #7  
Antiguo 14-11-2024
austral_es austral_es is offline
Miembro
 
Registrado: may 2017
Posts: 32
Poder: 0
austral_es Va por buen camino
Muchísimas gracias javipes y bmfranky!!

En el caso de javipes sino te pide la contraseña ¿puede ser porque tu certificado no la requiera? nuestros certificados sí la requieren (estamos usando unos propios y otros como el DNIe) y en los dos casos nos salta la ventana de windows pidiendo la contraseña, y esto era lo que queríamos evitar cargando el certificado desde el almacén y asignandole pal password.

De todas formas estamos estudiando los dos códigos, a ver si podemos tener algo en claro para nuestro caso.
Responder Con Cita
  #8  
Antiguo 14-11-2024
Avatar de bmfranky
bmfranky bmfranky is offline
Miembro
 
Registrado: may 2024
Ubicación: Gandia, Valencia
Posts: 599
Poder: 1
bmfranky Va por buen camino
Cita:
Empezado por austral_es Ver Mensaje
Muchísimas gracias javipes y bmfranky!!

En el caso de javipes sino te pide la contraseña ¿puede ser porque tu certificado no la requiera? nuestros certificados sí la requieren (estamos usando unos propios y otros como el DNIe) y en los dos casos nos salta la ventana de windows pidiendo la contraseña, y esto era lo que queríamos evitar cargando el certificado desde el almacén y asignandole pal password.

De todas formas estamos estudiando los dos códigos, a ver si podemos tener algo en claro para nuestro caso.
Hola, una vez en el almacen de windows, cuando lo abres con mi codigo, no pide nada, cuando lo agregaste a windows, ya lo pidio, otra cosa es el del DNI-E ,ese para poder usarlo, si o si has de introducir la clave, tambien al almacenarlo en la base de datos puedes guardar, cargar la contraseña que tu asignes encriptada, haciendo automatico el desbloqueo del mismo una vez ya guardada en la base de datos.
Solo volveria a pedir la clave, al actualizar el certificado, una vez caducado.
__________________
Uno se alegra de ser útil. (Isaac Asimov)
Responder Con Cita
Respuesta



Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Validar Password de un certificado adebonis Varios 0 28-03-2015 11:18:36
listar almacen de certificados de Windows JordiP Varios 1 26-08-2010 10:52:10
Como empiezo un sistema de almacen espalafox Varios 18 07-04-2007 01:01:39


La franja horaria es GMT +2. Ahora son las 13:07:58.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi