Ho un server WCF auto-ospitato che funziona come un servizio di Windows con l'account Sistema locale. Sto cercando di creare un certificato autofirmato a livello di codice in C# per l'utilizzo con un endpoint net.tcp utilizzando la sicurezza a livello di messaggio.Come creare un certificato autofirmato a livello di codice per il servizio WCF?
Sto usando il seguente codice che è strettamente basato sulla risposta accettata in How to create a self-signed certificate using C#? con alcune piccole modifiche che cercano di risolvere il mio problema.
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 1024;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA1");
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
//cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), "",
// mark the private key as exportable (this is usually what you want to do)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet
);
}
E io conservarlo con questo codice:
X509Store store = new X509Store(storeName, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(newCert);
store.Close();
questo crea il certificato e lo mette nell'archivio certificati LocalMachine. Il problema è che quando provo ad avviare il servizio WCF ottengo la seguente eccezione:
È probabile che il certificato "CN = myCertificate" non disponga di una chiave privata che sia in grado di scambiare le chiavi o che il processo non possa avere diritti di accesso per la chiave privata. Si prega di vedere l'eccezione interna per i dettagli. Eccezione interna: Keyset non esiste
L'uscita del campione FindPrivateKey (http://msdn.microsoft.com/en-us/library/aa717039%28v=vs.100%29.aspx) per il mio certificato è:
Private key directory:
C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Private key file name:
f0d47c7826b8ef5148b6d412f1c40024_4a8a026f-58e4-40f7-b779-3ae9b6aae1a7
posso vedere questo file 1.43KB in Esplora risorse. Se guardo le proprietà | Sicurezza vedo SISTEMA e amministratori entrambi con controllo completo.
Nella ricerca di questo errore ho visto molte risposte sulla chiave privata mancante o autorizzazioni errate. Non riesco a vedere quale sia il problema.
La cosa davvero strana è che se utilizzo il plugin Certificato mmc, vai al certificato e scegli Tutte le attività | Gestisci chiavi private ... Vedo le stesse impostazioni di sicurezza. Dopo aver visto questo anche se ho appena visualizzato la finestra di dialogo e premuto il pulsante Annulla, il certificato ora funziona correttamente in WCF. Posso semplicemente riavviare il servizio e tutto funziona perfettamente.
Se creo un certificato utilizzando MakeCert, funziona perfettamente dall'inizio. Non so cosa faccia in modo diverso.
Un'altra informazione che potrebbe non essere rilevante è che il certificato non viene inserito solo nel mio negozio dove gli ho detto di essere inserito, ma viene anche inserito nell'archivio "Autorità di certificazione intermedie". Non so perché o se è importante.
Quindi ... qualche idea su cosa sto facendo male?
AGGIORNAMENTO: Beh, questo non è solo un problema WCF. In sostanza, ho lo stesso problema quando provo a usare il certificato per collegarmi a un endpoint con http.sys usando HttpSetServiceConfiguration. Il metodo restituisce 1312 - "Una sessione di accesso specificata non esiste. Potrebbe essere già stata terminata". Questo non è in realtà il vero errore. Ho visto in caso di sicurezza registrare un errore di controllo che dice questo:
Cryptographic Parameters:
Provider Name: Microsoft Software Key Storage Provider
Algorithm Name: Not Available.
Key Name: {A23712D0-9A7B-4377-89DB-B1B39E3DA8B5}
Key Type: Machine key.
Cryptographic Operation:
Operation: Open Key.
Return Code: 0x80090011
0x80090011 è oggetto non è stato trovato. Quindi questo sembra essere lo stesso problema. Ancora una volta, dopo aver aperto la finestra di dialogo Gestisci chiavi private per il certificato, questo funziona perfettamente anche.
Sto ancora cercando la causa del problema.
AGGIORNAMENTO # 2: sono riuscito a farlo funzionare utilizzando la risposta accettata di seguito. È interessante notare che questo codice ora sembra mettere il certificato nell'archivio della macchina senza chiamare il codice X509Store. Io chiamo ancora il codice perché non ne sono sicuro e non danneggia nulla. Ecco il codice finale che sto usando per creare il certificato.
static public X509Certificate2 CreateSelfSignedCertificate(string subjectName, TimeSpan expirationLength)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now.Date;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = cert.NotBefore + expirationLength;
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), "",
// mark the private key as exportable (this is usually what you want to do)
// mark private key to go into the Machine store instead of the current users store
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet
);
}
Qualcuno sa come caricare un certificato esistente per nome descrittivo? –
Grazie per avermi fornito un aggiornamento .. Avevo un problema simile e non riuscivo a individuare cosa stavo facendo male, mi hai aiutato molto con questo post! :) – Spyral