2009-02-24 15 views
8

Estamos desarrollando una serie de servicios WCF. las solicitudes cruzarán un límite de dominio; es decir, los clientes se están ejecutando en un dominio y los servidores que manejan las solicitudes están en un dominio diferente (de producción). Sé cómo proteger este enlace con SSL y certificados. Solicitaremos a los usuarios sus nombres de usuario y contraseñas en el dominio de producción y los pasaremos a los encabezados de SOAP.¿Cómo evito los certificados SSL durante el desarrollo para un punto final WCF que estará protegido durante la producción?

Mi problema es qué hacer durante el desarrollo y las pruebas "beta". Sé que puedo obtener un certificado temporal y usarlo durante el desarrollo. Me pregunto cuáles son mis alternativas a este enfoque. ¿Qué han hecho otros en esta situación?

Actualización: No estoy seguro de haber obtenido una "buena" respuesta a mi pregunta. Soy parte de un gran equipo (más de 50) de desarrolladores. La organización es bastante ágil. Cualquiera de los desarrolladores podría terminar trabajando en el proyecto que usa WCF. De hecho, varios de los otros proyectos están haciendo algo similar, pero para diferentes sitios web y servicios. Lo que estaba buscando era una forma en que podía hacer que cualquiera entrara y trabajara en este proyecto en particular por unos días sin tener que pasar por varios aros. La instalación del certificado de desarrollo es uno de esos aros. Entiendo completamente que "dogfooding" la estructura de WCF durante el desarrollo es la mejor práctica. La mayoría de las respuestas dieron eso como la respuesta. Quería saber qué, en todo caso, tenía sentido que fuera distinto a "obtener un certificado de prueba (o dos) e instalarlo en todos los cuadros de desarrollador".

Jon

Respuesta

0

¿Qué hay de cambiar la configuración entre el desarrollo y la producción?

0

Mi sugerencia sería considerar un par de diferentes enfoques:

para el desarrollo -> Hay maneras de generar un certificado SSL a nivel local a fin de que las pruebas con https puede hacerse en un ambiente que usted tiene control total sobre .

Para pruebas "beta" -> Considere obtener un segundo certificado para esto, ya que puede haber una necesidad continua de realizar algunas pruebas beta entre versiones, por lo que es probable que se pueda utilizar una y otra vez.

2

Realmente desea que su entorno de desarrollo combine la producción tanto como sea posible. WCF verificará las listas de revocación durante la negociación de transporte o la comprobación de firmas y los certificados autofirmados, o las certificaciones falsas que utilizan makecert no son compatibles con las CRL.

Si tiene una máquina de repuesto, puede usar los Servicios de Certificado de Windows (gratis con Server 2003 y 2008). Esto proporciona una CA y puede solicitar certificados (SSL o cliente) a partir de ella. Tiene que ser una máquina de repuesto, ya que se configura en el sitio web predeterminado y se desordena por completo si ya lo ha ajustado. También publica CRL. Todo lo que tendría que hacer es instalar el certificado raíz para la CA en sus cajas de desarrollo y listo.

1

Tiene la opción de generar un certificado para usar en desarrollo o deshabilitar el uso de certificados a través del archivo de configuración. Yo recomendaría usar un certificado también en desarrollo.

5

ACTUALIZACIÓN: En realidad usamos el Keith Brown solution mucho más simple ahora, vea el código fuente que ha proporcionado. Ventaja: no hay código no administrado para mantener.

Si aún quieres ver cómo hacerlo usando C/C++, sigue leyendo ...

Solo recomendado para el uso en Desarrollo, por supuesto, no en producción, pero hay una forma de baja fricción para generar certificados X.509 (sin recurrir a makecert.exe).

Si tiene acceso a CryptoAPI en Windows, la idea es que use las llamadas CryptoAPI para generar claves RSA públicas y privadas, firme y codifique un nuevo certificado X.509, colóquelo en un almacén de certificados solo de memoria, y luego use PFXExportCertStore() para generar los bytes .pfx que luego puede pasar al constructor X509Certificate2.

Una vez que tenga una instancia X509Certificate2, puede establecerla como una propiedad de los objetos WCF adecuados, y las cosas simplemente comienzan a funcionar.

Tengo un código de ejemplo que he escrito, no hay garantías de ningún tipo de curso, y necesitarás un poco de experiencia C para escribir los bits que tienen que ser administrados (sería mucho más doloroso escriba la P/Invoke para todas las llamadas CryptoAPI que tener esa parte en C/C++).

Ejemplo C# código que utiliza la función auxiliar no administrado:

public X509Certificate2 GenerateSelfSignedCertificate(string issuerCommonName, string keyPassword) 
    { 
     int pfxSize = -1; 
     IntPtr pfxBufferPtr = IntPtr.Zero; 
     IntPtr errorMessagePtr = IntPtr.Zero; 

     try 
     { 
      if (!X509GenerateSelfSignedCertificate(KeyContainerName, issuerCommonName, keyPassword, ref pfxSize, ref pfxBufferPtr, ref errorMessagePtr)) 
      { 
       string errorMessage = null; 

       if (errorMessagePtr != IntPtr.Zero) 
       { 
        errorMessage = Marshal.PtrToStringUni(errorMessagePtr); 
       } 

       throw new ApplicationException(string.Format("Failed to generate X.509 server certificate. {0}", errorMessage ?? "Unspecified error.")); 
      } 
      if (pfxBufferPtr == IntPtr.Zero) 
      { 
       throw new ApplicationException("Failed to generate X.509 server certificate. PFX buffer not initialized."); 
      } 
      if (pfxSize <= 0) 
      { 
       throw new ApplicationException("Failed to generate X.509 server certificate. PFX buffer size invalid."); 
      } 

      byte[] pfxBuffer = new byte[pfxSize]; 
      Marshal.Copy(pfxBufferPtr, pfxBuffer, 0, pfxSize); 
      return new X509Certificate2(pfxBuffer, keyPassword); 
     } 
     finally 
     { 
      if (pfxBufferPtr != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(pfxBufferPtr); 
      } 
      if (errorMessagePtr != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(errorMessagePtr); 
      } 
     } 
    } 

La aplicación X509GenerateSelfSignedCertificate función podría ser algo como esto (que necesita WinCrypt.h):

BOOL X509GenerateSelfSignedCertificate(LPCTSTR keyContainerName, LPCTSTR issuerCommonName, LPCTSTR keyPassword, DWORD *pfxSize, BYTE **pfxBuffer, LPTSTR *errorMessage) 
{ 
    // Constants 
#define CERT_DN_ATTR_COUNT 1 
#define SIZE_SERIALNUMBER  8 
#define EXPIRY_YEARS_FROM_NOW 2 
#define MAX_COMMON_NAME  8192 
#define MAX_PFX_SIZE   65535 

    // Declarations 
    HCRYPTPROV hProv = NULL; 
    BOOL result = FALSE; 

    // Sanity 

    if (pfxSize != NULL) 
    { 
     *pfxSize = -1; 
    } 
    if (pfxBuffer != NULL) 
    { 
     *pfxBuffer = NULL; 
    } 
    if (errorMessage != NULL) 
    { 
     *errorMessage = NULL; 
    } 

    if (keyContainerName == NULL || _tcslen(issuerCommonName) <= 0) 
    { 
     SetOutputErrorMessage(errorMessage, _T("Key container name must not be NULL or an empty string.")); 
     return FALSE; 
    } 
    if (issuerCommonName == NULL || _tcslen(issuerCommonName) <= 0) 
    { 
     SetOutputErrorMessage(errorMessage, _T("Issuer common name must not be NULL or an empty string.")); 
     return FALSE; 
    } 
    if (keyPassword == NULL || _tcslen(keyPassword) <= 0) 
    { 
     SetOutputErrorMessage(errorMessage, _T("Key password must not be NULL or an empty string.")); 
     return FALSE; 
    } 

    // Start generating 
    USES_CONVERSION; 

    if (CryptAcquireContext(&hProv, keyContainerName, MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_MACHINE_KEYSET) || 
     CryptAcquireContext(&hProv, keyContainerName, MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) 
    { 
     HCRYPTKEY hKey = NULL; 

     // Generate 1024-bit RSA keypair. 
     if (CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE | RSA1024BIT_KEY, &hKey)) 
     { 
      DWORD pkSize = 0; 
      PCERT_PUBLIC_KEY_INFO pkInfo = NULL; 

      // Export public key for use by certificate signing. 
      if (CryptExportPublicKeyInfo(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, NULL, &pkSize) && 
       (pkInfo = (PCERT_PUBLIC_KEY_INFO)LocalAlloc(0, pkSize)) && 
       CryptExportPublicKeyInfo(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, pkInfo, &pkSize)) 
      { 
       CERT_RDN_ATTR certDNAttrs[CERT_DN_ATTR_COUNT]; 
       CERT_RDN certDN[CERT_DN_ATTR_COUNT] = {{1, &certDNAttrs[0]}}; 
       CERT_NAME_INFO certNameInfo = {CERT_DN_ATTR_COUNT, &certDN[0]}; 
       DWORD certNameSize = -1; 
       BYTE *certNameData = NULL; 

       certDNAttrs[0].dwValueType = CERT_RDN_UNICODE_STRING; 
       certDNAttrs[0].pszObjId = szOID_COMMON_NAME; 
       certDNAttrs[0].Value.cbData = (DWORD)(_tcslen(issuerCommonName) * sizeof(WCHAR)); 
       certDNAttrs[0].Value.pbData = (BYTE*)T2W((LPTSTR)issuerCommonName); 

       // Encode issuer name into certificate name blob. 
       if (CryptEncodeObject(X509_ASN_ENCODING, X509_NAME, &certNameInfo, NULL, &certNameSize) && 
        (certNameData = (BYTE*)LocalAlloc(0, certNameSize)) && 
        CryptEncodeObject(X509_ASN_ENCODING, X509_NAME, &certNameInfo, certNameData, &certNameSize)) 
       { 
        CERT_NAME_BLOB issuerName; 
        CERT_INFO certInfo; 
        SYSTEMTIME systemTime; 
        FILETIME notBefore; 
        FILETIME notAfter; 
        BYTE serialNumber[SIZE_SERIALNUMBER]; 
        DWORD certSize = -1; 
        BYTE *certData = NULL; 

        issuerName.cbData = certNameSize; 
        issuerName.pbData = certNameData; 

        // Certificate should be valid for a decent window of time. 
        ZeroMemory(&certInfo, sizeof(certInfo)); 
        GetSystemTime(&systemTime); 
        systemTime.wYear -= 1; 
        SystemTimeToFileTime(&systemTime, &notBefore); 
        systemTime.wYear += EXPIRY_YEARS_FROM_NOW; 
        SystemTimeToFileTime(&systemTime, &notAfter); 

        // Generate a throwaway serial number. 
        if (CryptGenRandom(hProv, SIZE_SERIALNUMBER, serialNumber)) 
        { 
         certInfo.dwVersion = CERT_V3; 
         certInfo.SerialNumber.cbData = SIZE_SERIALNUMBER; 
         certInfo.SerialNumber.pbData = serialNumber; 
         certInfo.SignatureAlgorithm.pszObjId = szOID_RSA_MD5RSA; 
         certInfo.Issuer = issuerName; 
         certInfo.NotBefore = notBefore; 
         certInfo.NotAfter = notAfter; 
         certInfo.Subject = issuerName; 
         certInfo.SubjectPublicKeyInfo = *pkInfo; 

         // Now sign and encode it. 
         if (CryptSignAndEncodeCertificate(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, (LPVOID)&certInfo, &(certInfo.SignatureAlgorithm), NULL, NULL, &certSize) && 
          (certData = (BYTE*)LocalAlloc(0, certSize)) && 
          CryptSignAndEncodeCertificate(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, (LPVOID)&certInfo, &(certInfo.SignatureAlgorithm), NULL, certData, &certSize)) 
         { 
          HCERTSTORE hCertStore = NULL; 

          // Open a new temporary store. 
          if ((hCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL))) 
          { 
           PCCERT_CONTEXT certContext = NULL; 

           // Add to temporary store so we can use the PFX functions to export a store + private keys in PFX format. 
           if (CertAddEncodedCertificateToStore(hCertStore, X509_ASN_ENCODING, certData, certSize, CERT_STORE_ADD_NEW, &certContext)) 
           { 
            CRYPT_KEY_PROV_INFO keyProviderInfo; 

            // Link keypair to certificate (without this the keypair gets "lost" on export). 
            ZeroMemory(&keyProviderInfo, sizeof(keyProviderInfo)); 
            keyProviderInfo.pwszContainerName = T2W((LPTSTR)keyContainerName); 
            keyProviderInfo.pwszProvName = MS_DEF_RSA_SCHANNEL_PROV_W; /* _W used intentionally. struct hardcodes LPWSTR. */ 
            keyProviderInfo.dwProvType = PROV_RSA_SCHANNEL; 
            keyProviderInfo.dwFlags = CRYPT_MACHINE_KEYSET; 
            keyProviderInfo.dwKeySpec = AT_KEYEXCHANGE; 

            // Finally, export to PFX and provide to caller. 
            if (CertSetCertificateContextProperty(certContext, CERT_KEY_PROV_INFO_PROP_ID, 0, (LPVOID)&keyProviderInfo)) 
            { 
             CRYPT_DATA_BLOB pfxBlob; 
             DWORD pfxExportFlags = EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY; 

             // Calculate size required. 
             ZeroMemory(&pfxBlob, sizeof(pfxBlob)); 
             if (PFXExportCertStore(hCertStore, &pfxBlob, T2CW(keyPassword), pfxExportFlags)) 
             { 
              pfxBlob.pbData = (BYTE *)LocalAlloc(0, pfxBlob.cbData); 

              if (pfxBlob.pbData != NULL) 
              { 
               // Now export. 
               if (PFXExportCertStore(hCertStore, &pfxBlob, T2CW(keyPassword), pfxExportFlags)) 
               { 
                if (pfxSize != NULL) 
                { 
                 *pfxSize = pfxBlob.cbData; 
                } 
                if (pfxBuffer != NULL) 
                { 
                 // Caller must free this. 
                 *pfxBuffer = pfxBlob.pbData; 
                } 
                else 
                { 
                 // Caller did not provide target pointer to receive buffer, free ourselves. 
                 LocalFree(pfxBlob.pbData); 
                } 

                result = TRUE; 
               } 
               else 
               { 
                SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format (0x%08x)."), GetLastError()); 
               } 
              } 
              else 
              { 
               SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format, buffer allocation failure (0x%08x)."), GetLastError()); 
              } 
             } 
             else 
             { 
              SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format, failed to calculate buffer size (0x%08x)."), GetLastError()); 
             } 
            } 
            else 
            { 
             SetOutputErrorMessage(errorMessage, _T("Failed to set certificate key context property (0x%08x)."), GetLastError()); 
            } 
           } 
           else 
           { 
            SetOutputErrorMessage(errorMessage, _T("Failed to add certificate to temporary certificate store (0x%08x)."), GetLastError()); 
           } 

           CertCloseStore(hCertStore, 0); 
           hCertStore = NULL; 
          } 
          else 
          { 
           SetOutputErrorMessage(errorMessage, _T("Failed to create temporary certificate store (0x%08x)."), GetLastError()); 
          } 
         } 
         else 
         { 
          SetOutputErrorMessage(errorMessage, _T("Failed to sign/encode certificate or out of memory (0x%08x)."), GetLastError()); 
         } 

         if (certData != NULL) 
         { 
          LocalFree(certData); 
          certData = NULL; 
         } 
        } 
        else 
        { 
         SetOutputErrorMessage(errorMessage, _T("Failed to generate certificate serial number (0x%08x)."), GetLastError()); 
        } 
       } 
       else 
       { 
        SetOutputErrorMessage(errorMessage, _T("Failed to encode X.509 certificate name into ASN.1 or out of memory (0x%08x)."), GetLastError()); 
       } 

       if (certNameData != NULL) 
       { 
        LocalFree(certNameData); 
        certNameData = NULL; 
       } 
      } 
      else 
      { 
       SetOutputErrorMessage(errorMessage, _T("Failed to export public key blob or out of memory (0x%08x)."), GetLastError()); 
      } 

      if (pkInfo != NULL) 
      { 
       LocalFree(pkInfo); 
       pkInfo = NULL; 
      } 
      CryptDestroyKey(hKey); 
      hKey = NULL; 
     } 
     else 
     { 
      SetOutputErrorMessage(errorMessage, _T("Failed to generate public/private keypair for certificate (0x%08x)."), GetLastError()); 
     } 

     CryptReleaseContext(hProv, 0); 
     hProv = NULL; 
    } 
    else 
    { 
     SetOutputErrorMessage(errorMessage, _T("Failed to acquire cryptographic context (0x%08x)."), GetLastError()); 
    } 

    return result; 
} 

void 
SetOutputErrorMessage(LPTSTR *errorMessage, LPCTSTR formatString, ...) 
{ 
#define MAX_ERROR_MESSAGE 1024 
    va_list va; 

    if (errorMessage != NULL) 
    { 
     size_t sizeInBytes = (MAX_ERROR_MESSAGE * sizeof(TCHAR)) + 1; 
     LPTSTR message = (LPTSTR)LocalAlloc(0, sizeInBytes); 

     va_start(va, formatString); 
     ZeroMemory(message, sizeInBytes); 
     if (_vstprintf_s(message, MAX_ERROR_MESSAGE, formatString, va) == -1) 
     { 
      ZeroMemory(message, sizeInBytes); 
      _tcscpy_s(message, MAX_ERROR_MESSAGE, _T("Failed to build error message")); 
     } 

     *errorMessage = message; 

     va_end(va); 
    } 
} 

Hemos usado este para generar Certificados SSL al inicio, lo cual está bien cuando solo se quiere probar el cifrado y no verificar la confianza/identidad, y solo se tarda unos 2-3 segundos en generar.

1

Extendiendo la respuesta de Leon Breedt, para generar el certificado en memoria x509 puede usar el código fuente de Keith Elder's SelfCert.

using (CryptContext ctx = new CryptContext()) 
{ 
    ctx.Open(); 

    var cert = ctx.CreateSelfSignedCertificate(
     new SelfSignedCertProperties 
     { 
      IsPrivateKeyExportable = true, 
      KeyBitLength = 4096, 
      Name = new X500DistinguishedName("cn=InMemoryTestCert"), 
      ValidFrom = DateTime.Today.AddDays(-1), 
      ValidTo = DateTime.Today.AddYears(5), 
     }); 

    var creds = new ServiceCredentials(); 
    creds.UserNameAuthentication.CustomUserNamePasswordValidator = new MyUserNamePasswordValidator(); 
    creds.ServiceCertificate.Certificate = cert; 
    serviceHost.Description.Behaviors.Add(creds); 
} 
+0

Donde su jQuery pad! –

+0

En realidad usamos la solución de Keith Elder en la empresa ahora, mantener archivos DLL de C++ separados era demasiado doloroso. –

+0

@Leon Breedt @Paul Stovell: Creo que ambos se refieren a Keith Brown, ¿verdad? –

Cuestiones relacionadas