2009-02-24 7 views
8

Rozwijamy szereg usług WCF. wnioski przekroczą granicę domeny; to znaczy, że klienci działają w jednej domenie, a serwery obsługujące żądania znajdują się w innej (produkcyjnej) domenie. Wiem, jak zabezpieczyć ten link za pomocą SSL i certyfikatów. Będziemy używać użytkowników do ich nazw użytkowników i haseł w domenie produkcyjnej i przekazywać je w nagłówkach SOAP.Jak uniknąć certyfikatów SSL podczas tworzenia punktu końcowego WCF, które będą zabezpieczone podczas produkcji

Mój problem polega na tym, co zrobić podczas programowania i testów "beta". Wiem, że mogę uzyskać tymczasowy certyfikat i używać go podczas programowania. Zastanawiam się, jakie są moje alternatywy dla tego podejścia. Co zrobili inni w tej sytuacji?

Aktualizacja: Nie jestem pewien, czy dostałem "dobrą" odpowiedź na moje pytanie. Jestem częścią dużego zespołu (50+) programistów. Organizacja jest dość zwinna. Każdy z programistów może skończyć pracę nad projektem korzystającym z WCF. W rzeczywistości kilka innych projektów robi coś podobnego, ale dla różnych stron internetowych i usług. To, czego szukałem, to sposób, w jaki mógłbym pozwolić każdemu wejść i pracować nad tym konkretnym projektem przez kilka dni bez konieczności przeskakiwania przez kilka kółek. Instalacja certyfikatu programistycznego jest jednym z tych kółek. W pełni rozumiem, że "dogfooding" struktury WCF podczas rozwoju jest najlepszą praktyką. Większość odpowiedzi dała to jako odpowiedź. Chciałem wiedzieć, co, jeśli w ogóle, miało sens, że było inne niż "dostać certyfikat (lub dwa) i zainstalować go na wszystkich skrzynkach deweloperskich".

Jon

Odpowiedz

0

Co powiesz na zmianę konfiguracji między rozwojem a produkcją?

0

Moja sugestia byłoby rozważyć kilka różnych podejść:

na rzecz rozwoju -> Istnieją sposoby, aby wygenerować certyfikat SSL lokalnie tak, że testy z https mogą być wykonywane w środowisku, które masz całkowitą kontrolę nad .

W przypadku testów "beta" - należy rozważyć uzyskanie drugiego certyfikatu, ponieważ może istnieć ciągła potrzeba przeprowadzenia testów beta między wydaniami, aby można było z nich wielokrotnie korzystać.

2

Naprawdę chcesz, aby twoje środowisko programistyczne w jak największym stopniu odpowiadało produkcji. WCF będzie sprawdzać listy odwołania podczas negocjacji transportu lub sprawdzania podpisu i samopodpisanych certyfikatów, lub fałszywe certyfikaty za pomocą makecert nie obsługują list CRL.

Jeśli masz zapasowy komputer, możesz skorzystać z usług certyfikatów Windows (za darmo z Server 2003 i 2008). Zapewnia to urząd certyfikacji i można żądać od niego certyfikatów (SSL lub klienta). Musi to być zapasowa maszyna, która ustawia się w domyślnej witrynie internetowej i całkowicie przestaje działać, jeśli już to zrobiliście. Publikuje również listy CRL. Wszystko, co musisz zrobić, to zainstalować certyfikat główny dla urzędu certyfikacji na pudełkach programistycznych i dalej.

1

Istnieje możliwość wygenerowania certyfikatu do użycia podczas programowania lub wyłączenia używania certyfikatów w pliku konfiguracyjnym. Zalecałbym używanie certyfikatu również w fazie rozwoju.

5

UPDATE: My faktycznie użyć znacznie prostsze Keith Brown solution zamiast tego teraz zobaczyć kod źródłowy on przewidziany. Zaleta: Brak niezarządzanego kodu do utrzymania.

Jeśli nadal chcesz zobaczyć, jak to zrobić, używając C/C++, czytaj dalej ...

Zalecane tylko do użytku w programie Rozwój, a nie do produkcji, ale istnieje niski współczynnik tarcia do generowania certyfikatów X.509 (bez odwoływania się do makecert.exe).

Jeśli masz dostęp do CryptoAPI w systemie Windows, chodzi o to, że używasz wywołań CryptoAPI do generowania publicznych i prywatnych kluczy RSA, podpisywania i kodowania nowego certyfikatu X.509, umieszczania go w magazynie certyfikatów tylko pamięci i następnie użyj PFXExportCertStore(), aby wygenerować bajty .pfx, które następnie możesz przekazać do konstruktora X509Certificate2.

Po utworzeniu instancji X509Certificate2 można ustawić ją jako właściwość odpowiednich obiektów WCF, a rzeczy po prostu zaczynają działać.

Mam przykładowy kod, który napisałem, brak gwarancji jakiegokolwiek rodzaju, a do napisania bitów, które muszą być niezarządzane, potrzebne jest trochę doświadczenia C. (byłoby o wiele trudniej zapisz P/Invoke dla wszystkich wywołań CryptoAPI, niż żeby ta część była w C/C++).

Przykład kodu C#, który używa niezarządzanego funkcji pomocnika:

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

Realizacja X509GenerateSelfSignedCertificate funkcja mogłaby wyglądać następująco (trzeba 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); 
    } 
} 

Użyliśmy tego celu wygenerowania Certyfikaty SSL przy starcie, co jest dobre, gdy chcesz tylko przetestować szyfrowanie i nie weryfikować zaufania/tożsamości, a generowanie trwa tylko około 2-3 sekund.

1

Po rozszerzeniu odpowiedzi Leona Breedta na wygenerowanie certyfikatu x509 w pamięci można użyć kodu źródłowego z 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

Gdzie jest twój pad jQuery! –

+0

Obecnie używamy rozwiązania Keitha Eldera w firmie, utrzymywanie oddzielnych bibliotek DLL C++ było zbyt wielkim bólem. –

+0

@Leon Breedt @Paul Stovell: Myślę, że oboje mówicie Keith Brown, prawda. –