a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
248 lines
6.9 KiB
C#
248 lines
6.9 KiB
C#
//
|
|
// crlupdate.cs: CRL downloader / updater
|
|
//
|
|
// Author:
|
|
// Sebastien Pouliot <sebastien@ximian.com>
|
|
//
|
|
// Copyright (C) 2011 Novell, Inc (http://www.novell.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
|
|
using Mono.Security.X509;
|
|
using Mono.Security.X509.Extensions;
|
|
|
|
[assembly: AssemblyTitle ("Mono CRL Updater")]
|
|
[assembly: AssemblyDescription ("Download and update X.509 certificate revocation lists from your stores.")]
|
|
|
|
namespace Mono.Tools {
|
|
|
|
class CrlUpdater {
|
|
|
|
static private void Header ()
|
|
{
|
|
Console.WriteLine (new AssemblyInfo ().ToString ());
|
|
}
|
|
|
|
static private void Help ()
|
|
{
|
|
Console.WriteLine ("Usage: crlupdate [-m] [-v] [-f] [-?]");
|
|
Console.WriteLine ();
|
|
Console.WriteLine ("\t-m\tuse the machine certificate store (default to user)");
|
|
Console.WriteLine ("\t-v\tverbose mode (display status for every steps)");
|
|
Console.WriteLine ("\t-f\tforce download (and replace existing CRL)");
|
|
Console.WriteLine ("\t-?\tDisplay this help message");
|
|
Console.WriteLine ();
|
|
}
|
|
|
|
static X509Certificate FindCrlIssuer (string name, byte[] aki, X509CertificateCollection col)
|
|
{
|
|
foreach (X509Certificate cert in col) {
|
|
if (name != cert.SubjectName)
|
|
continue;
|
|
if ((aki == null) || Compare (aki, GetSubjectKeyIdentifier (cert.Extensions ["2.5.29.14"])))
|
|
return cert;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static X509Certificate FindCrlIssuer (X509Crl crl)
|
|
{
|
|
string name = crl.IssuerName;
|
|
byte [] aki = GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
|
|
X509Certificate cert = FindCrlIssuer (name, aki, X509StoreManager.IntermediateCACertificates);
|
|
if (cert != null)
|
|
return cert;
|
|
return FindCrlIssuer (name, aki, X509StoreManager.TrustedRootCertificates);
|
|
}
|
|
|
|
static X509Chain chain = new X509Chain ();
|
|
|
|
static bool VerifyCrl (X509Crl crl)
|
|
{
|
|
X509Certificate issuer = FindCrlIssuer (crl);
|
|
if (issuer == null)
|
|
return false;
|
|
|
|
if (!crl.VerifySignature (issuer))
|
|
return false;
|
|
|
|
chain.Reset ();
|
|
return chain.Build (issuer);
|
|
}
|
|
|
|
static void Download (string url, X509Store store)
|
|
{
|
|
if (verbose)
|
|
Console.WriteLine ("Downloading: {0}", url);
|
|
|
|
WebClient wc = new WebClient ();
|
|
string error = "download";
|
|
try {
|
|
byte [] data = wc.DownloadData (url);
|
|
error = "decode";
|
|
X509Crl crl = new X509Crl (data);
|
|
error = "import";
|
|
// warn if CRL is not current - but still allow it to be imported
|
|
if (!crl.IsCurrent && verbose)
|
|
Console.WriteLine ("WARNING: CRL is not current: {0}", url);
|
|
|
|
// only import the CRL if its signature is valid and coming from a trusted root
|
|
if (VerifyCrl (crl))
|
|
store.Import (crl);
|
|
else
|
|
Console.WriteLine ("ERROR: could not validate CRL: {0}", url);
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine ("ERROR: could not {0}: {1}", error, url);
|
|
if (verbose) {
|
|
Console.WriteLine (e);
|
|
Console.WriteLine ();
|
|
}
|
|
}
|
|
}
|
|
|
|
static byte [] GetAuthorityKeyIdentifier (X509Extension ext)
|
|
{
|
|
if (ext == null)
|
|
return null;
|
|
|
|
AuthorityKeyIdentifierExtension aki = new AuthorityKeyIdentifierExtension (ext);
|
|
return aki.Identifier;
|
|
}
|
|
|
|
static byte [] GetSubjectKeyIdentifier (X509Extension ext)
|
|
{
|
|
if (ext == null)
|
|
return null;
|
|
|
|
SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
|
|
return ski.Identifier;
|
|
}
|
|
|
|
static bool Compare (byte [] a, byte [] b)
|
|
{
|
|
if (a == null)
|
|
return (b == null);
|
|
else if (b == null)
|
|
return false;
|
|
|
|
if (a.Length != b.Length)
|
|
return false;
|
|
|
|
for (int i = 0; i < a.Length; i++) {
|
|
if (a [i] != b [i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static X509Crl FindCrl (X509Certificate cert, X509Store store)
|
|
{
|
|
string name = cert.SubjectName;
|
|
byte [] ski = GetSubjectKeyIdentifier (cert.Extensions ["2.5.29.14"]);
|
|
foreach (X509Crl crl in store.Crls) {
|
|
if (crl.IssuerName != name)
|
|
continue;
|
|
if ((ski == null) || Compare (ski, GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"])))
|
|
return crl;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static void UpdateStore (X509Store store)
|
|
{
|
|
// for each certificate
|
|
foreach (X509Certificate cert in store.Certificates) {
|
|
|
|
// do we already have a matching CRL ? (or are we forced to download?)
|
|
X509Crl crl = force ? null : FindCrl (cert, store);
|
|
// without a CRL (or with a CRL in need of updating)
|
|
if ((crl == null) || !crl.IsCurrent) {
|
|
X509Extension ext = cert.Extensions ["2.5.29.31"];
|
|
if (ext == null) {
|
|
if (verbose)
|
|
Console.WriteLine ("WARNING: No cRL distribution point found for '{0}'", cert.SubjectName);
|
|
continue;
|
|
}
|
|
|
|
CRLDistributionPointsExtension crlDP = new CRLDistributionPointsExtension (ext);
|
|
foreach (var dp in crlDP.DistributionPoints) {
|
|
string name = dp.Name.Trim ();
|
|
if (name.StartsWith ("URL="))
|
|
Download (name.Substring (4), store);
|
|
else if (verbose)
|
|
Console.WriteLine ("WARNING: Unsupported distribution point: '{0}'", name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool verbose = false;
|
|
static bool force = false;
|
|
|
|
static int Main (string [] args)
|
|
{
|
|
bool machine = false;
|
|
|
|
for (int i = 0; i < args.Length; i++) {
|
|
switch (args [i]) {
|
|
case "-m":
|
|
case "--m":
|
|
machine = true;
|
|
break;
|
|
case "-v":
|
|
case "--v":
|
|
verbose = true;
|
|
break;
|
|
case "-f":
|
|
case "--f":
|
|
force = true;
|
|
break;
|
|
case "-help":
|
|
case "--help":
|
|
case "-?":
|
|
case "--?":
|
|
Help ();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
try {
|
|
X509Stores stores = ((machine) ? X509StoreManager.LocalMachine : X509StoreManager.CurrentUser);
|
|
// for all store (expect Untrusted)
|
|
UpdateStore (stores.TrustedRoot);
|
|
UpdateStore (stores.IntermediateCA);
|
|
UpdateStore (stores.Personal);
|
|
UpdateStore (stores.OtherPeople);
|
|
return 0;
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine ("ERROR: Unexpected exception: {0}", e);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|