SvcUtil permette di creare automaticamente la configurazione client di un servizio WCF.
Tra le cose che vengono create c'è l'encoding in Base64 della chiave pubblica del certificato usato dal server
1: <identity>
2: <certificate encodedValue="AwAAAA ...." />
3: </identity>
Naturalmente SvcUtil ricava questa informazione dai metadati.
Sfortunatamente ci sono casi in cui la configurazione del servizio è complessa e non si riesce in modo semplice ad abilitare l'endpoint dei metadati. Guardacaso mi è capitato e il certificato delle due macchine di sviluppo erano diverse.
Apparentemente la soluzione è semplice:
1: private static string GetEncoded(X509Certificate2 cert)
2: {
3: byte[] export = cert.Export(X509ContentType.SerializedCert);
4: string encoded = Convert.ToBase64String(export);
5: return encoded;
6: }
Questo valore è corretto per molti certificati ma ha un side-effect pericolosissimo. Il metodo Export esporta il certificato nella sua interezza, cioè compresa la chiave privata, se presente. Quando viene fatto il deploy di un certificato nello store si può scegliere se installarlo con o senza chiave privata.
Questo significa che la funzione GetEncoded ricava la stessa stringa di SvcUtil per i certificati con sola chiave pubblica, ma dal momento in cui ci imbattiamo in un certificato con chiave privata esportiamo anche quella. Dare una chiave privata in giro equivale a dare una copia delle chiavi di casa a tutti quelli che incontriamo. Non è proprio quello che si dice una cosa saggia.
Così ho cercato di eliminare la chiave privata dal certificato prima di esportarla. Dopo un po' di tentativi andati a vuoto mi sono rivolto al buon amico Mario Fontana (anche se lo tiene nascosto bene, un po' di codice delle CAPICOM viene dalla sua tastiera) che mi ha fatto notare che il formato DER dei certificati si può ottenere semplicemente esportando con l'opzione "X509ContentType.Cert".
1: byte[] der = certRaf.Export(X509ContentType.Cert); // solo public key
A noi però interessa la SerializedCert e quindi non ci resta che re-importare il certificato appena esportato con "Cert"
1: private static X509Certificate2 ImportFromBlob(byte[] certBlob)
2: {
3: X509Certificate2Collection certs = new X509Certificate2Collection();
4: certs.Import(certBlob);
5: X509Certificate2 imported = certs[0];
6: return imported;
7: }
Ed infine richiamare la GetEncoded per ottenere la magica stringa che viene usata nella configurazione client di WCF.