8

多数の WCF サービスを開発しています。リクエストはドメインの境界を越えます。つまり、クライアントは 1 つのドメインで実行され、要求を処理するサーバーは別の (運用) ドメインにあります。このリンクを SSL と証明書で保護する方法を知っています。本番ドメインのユーザー名とパスワードをユーザーに確認し、それらを SOAP ヘッダーで渡します。

私の問題は、開発と「ベータ」テスト中に何をするかです。一時的な証明書を取得して、開発中に使用できることを知っています。このアプローチの代替手段は何だろうと思っています。この状況で他の人は何をしましたか?

更新: 私の質問に対して「良い」回答が得られたかどうかはわかりません。私は大規模な開発チーム (50 人以上) の一員です。組織はかなり機敏です。開発者のいずれかが、WCF を使用しているプロジェクトに取り組むことになる可能性があります。実際、他のプロジェクトのいくつかは、似たようなことを行っていますが、Web サイトやサービスは異なります。私が探していたのは、誰でも参加してこの特定のプロジェクトに数日間取り組むことができる方法でした。開発証明書をインストールすることは、それらのフープの 1 つです。開発中に WCF 構造を "ドッグフーディング" することがベスト プラクティスであることを十分に理解しています。ほとんどの回答はそれを答えとして与えました。私は、どちらかといえば、それ以外の意味をなすものを知りたかったのです」

ジョン

4

6 に答える 6

5

更新:実際には、これよりもはるかに単純なKeith Brown ソリューションを使用しています。彼が提供したソース コードを参照してください。利点: アンマネージ コードを維持する必要がありません。

C/C++ を使用してそれを行う方法を引き続き確認したい場合は、読み進めてください...

もちろん、本番環境ではなく開発環境でのみ使用することをお勧めしますが、X.509 証明書を生成する摩擦の少ない方法があります (に頼ることなくmakecert.exe)。

Windows で CryptoAPI にアクセスできる場合、CryptoAPI 呼び出しを使用して RSA 公開鍵と秘密鍵を生成し、新しい X.509 証明書に署名してエンコードし、それをメモリのみの証明書ストアに入れ、それを使用PFXExportCertStore()して生成するという考え方です。X509Certificate2コンストラクターに渡すことができる .pfx バイト。

インスタンスを作成したらX509Certificate2、それを適切な WCF オブジェクトのプロパティとして設定すると、作業が開始されます。

私が書いたサンプルコードがいくつかありますが、もちろんいかなる保証もありません。アンマネージする必要があるビットを書くには、C の経験が少し必要です (P/その部分を C/C++ にするよりも、すべての CryptoAPI 呼び出しを呼び出します)。

アンマネージ ヘルパー関数を使用する 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 秒しかかかりません。

于 2009-08-22T01:11:50.027 に答える
2

開発環境を本番環境に可能な限り一致させたいと本当に思っています。WCF は、トランスポート ネゴシエーションまたは署名チェック中に失効リストをチェックし、自己署名証明書、または makecert を使用した偽の証明書は CRL をサポートしません。

予備のマシンがある場合は、Windows 証明書サービス (Server 2003 および 2008 では無料) を使用できます。これにより CA が提供され、CA から証明書 (SSL またはクライアント) を要求できます。デフォルトの Web サイトの下に自分自身をセットアップし、すでに調整している場合は完全に台無しになるため、予備のマシンである必要があります。また、CRL も発行します。CA のルート証明書を開発ボックスにインストールするだけで済みます。

于 2009-02-24T16:42:07.323 に答える
1

Leon Breedt の回答を拡張して、Keith Elder の SelfCertのソース コードを使用できるインメモリ x509 証明書を生成します。

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);
}
于 2010-09-25T01:29:04.697 に答える
1

開発で使用する証明書を生成するか、構成ファイルを介して証明書の使用を無効にするオプションがあります。開発でも実際に証明書を使用することをお勧めします。

于 2009-02-24T16:45:19.797 に答える
0

開発と本番で構成を変えてみませんか?

于 2009-02-24T16:24:09.160 に答える
0

私の提案は、いくつかの異なるアプローチを検討することです。

開発用 -> 完全に制御できる環境で https を使用したテストを実行できるように、ローカルで SSL 証明書を生成する方法があります。

「ベータ」テストの場合 -> このための 2 つ目の証明書を取得することを検討してください。これは、リリース間でベータ テストを継続的に行う必要があり、何度も何度も使用できる可能性があるためです。

于 2009-02-24T16:30:09.520 に答える