2009-02-24 5 views
8

Мы разрабатываем ряд сервисов WCF. запросы будут пересекаться с границей домена; то есть клиенты работают в одном домене, а серверы, обрабатывающие запросы, находятся в другом (производственном) домене. Я знаю, как защитить эту ссылку с помощью SSL и сертификатов. Мы будем использовать пользователей для их имен пользователей и паролей в домене производства и передавать их в заголовках SOAP.Как избежать SSL-сертификатов во время разработки для конечной точки WCF, которая будет защищена во время производства

Моя проблема заключается в том, что делать во время разработки и тестирования «бета». Я знаю, что могу получить временный сертификат и использовать его во время разработки. Мне интересно, каковы мои альтернативы этому подходу. Что другие делали в этой ситуации?

Обновление: Я не уверен, что получил «хороший» ответ на мой вопрос. Я являюсь частью большой команды (50+) разработчиков. Организация достаточно подвижна. Любой из разработчиков может завершить работу над проектом, использующим WCF. На самом деле некоторые из других проектов делают что-то подобное, но для разных веб-сайтов и служб. То, что я искал, было способом, которым я мог бы заставить кого-нибудь войти и работать над этим конкретным проектом в течение нескольких дней, не перепрыгивая через несколько обручей. Установка сертификата разработки является одним из этих обручей. Я полностью понимаю, что «досуг» структуры WCF во время разработки - лучшая практика. Большинство ответов дали это как ответ. Я хотел знать, что, если что-нибудь имело смысл, было иначе, чем «получить тестовый сертификат (или два) и установить его на все ящики для разработчиков».

Jon

ответ

0

Как об изменении конфигурации между развитием и производством?

0

Мое предложение было бы рассмотреть несколько различных подходов:

Для развития -> Есть способы генерации сертификата SSL на местном уровне, так что тесты с HTTPS может быть сделано в среде, вы имеете полный контроль над ,

Для «бета-тестирования» - рассмотрите возможность получения второго сертификата для этого, поскольку может потребоваться непрерывная необходимость проведения бета-тестирования между выпусками, поэтому его можно использовать снова и снова.

2

Действительно, вы хотите, чтобы ваша среда разработки соответствовала производству в максимально возможной степени. WCF проверяет списки аннулирования во время согласования транспорта или проверки подписи и самоподписанных сертификатов, или поддельные сертификаты с использованием makecert не поддерживают CRL.

Если у вас есть запасная машина, вы можете использовать службы сертификации Windows (бесплатно с Server 2003 и 2008). Это обеспечивает CA, и вы можете запросить от него сертификаты (SSL или клиент). Он должен быть запасной машиной, поскольку он настраивается под веб-сайт по умолчанию и полностью запутывается, если вы уже изменили это. Он также публикует CRL. Все, что вам нужно сделать, - это установить корневой сертификат для ЦС в свои коробки разработки и уйти.

1

У вас есть возможность либо сгенерировать сертификат для использования в процессе разработки, либо отключить использование сертификатов через файл конфигурации. Я бы рекомендовал использовать сертификат также в разработке.

5

ОБНОВЛЕНИЕ: На самом деле мы используем гораздо более простой Keith Brown solution вместо этого сейчас, см. Исходный код, который он предоставил. Преимущество: нет неуправляемого кода для обслуживания.

Если вы все еще хотите посмотреть, как это сделать, используя C/C++, читайте дальше ...

Рекомендован только для использования в разработке, но не для производства, но существует способ с низким трением генерировать сертификаты X.509 (не прибегая к makecert.exe).

Если у вас есть доступ к CryptoAPI в Windows, идея заключается в том, что вы используете вызовы CryptoAPI для создания открытых и закрытых ключей RSA, подписываете и кодируете новый сертификат X.509, помещаете его в хранилище только для хранения данных и затем используйте PFXExportCertStore() для генерации байтов .pfx, которые затем можно передать в конструктор X509Certificate2.

Как только у вас есть экземпляр X509Certificate2, вы можете установить его как свойство соответствующих объектов WCF, и все начнет работать.

У меня есть пример кода, который я написал, никаких гарантий какого-либо курса, и вам потребуется немного опыта работы с C, чтобы записать бит, который должен быть неуправляемым (было бы намного больнее напишите P/Invoke для всех вызовов CryptoAPI, чем для того, чтобы эта часть была в C/C++).

Пример C# код, использующий неуправляемого вспомогательную функцию:

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); 
      } 
     } 
    } 

Реализация X509GenerateSelfSignedCertificate функция может пойти что-то вроде этого (вам нужно 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); 
    } 
} 

Мы использовали его для создания SSL-сертификаты при запуске, что прекрасно, если вы хотите протестировать шифрование и не проверять доверие/идентификацию, и для генерации требуется всего около 2-3 секунд.

1

Расширение ответа Leon Breedt, чтобы сгенерировать сертификат x509 в памяти, вы можете использовать исходный код от 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

Где ваша панель jQuery! – 2010-12-07 12:00:07

+0

Мы фактически используем решение Keith Elder in-house, поддерживая отдельные C++ DLL, что было слишком больно. –

+0

@ Leon Breedt @Paul Stovell: Я думаю, вы оба имеете в виду Кейта Брауна, не так ли? –