1 // 2 // CertificateFormatter.cs: Certificate Formatter (not GUI specific) 3 // 4 // Author: 5 // Sebastien Pouliot <sebastien@ximian.com> 6 // 7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com) 8 // (C) 2004 Novell (http://www.novell.com) 9 // 10 11 using System; 12 using System.Collections; 13 using System.Configuration; 14 using System.IO; 15 using System.Reflection; 16 using System.Security.Cryptography; 17 using System.Text; 18 19 using Mono.Security.X509; 20 using Mono.Security.X509.Extensions; 21 22 namespace Mono.Tools.CertView { 23 24 public class CertificateFormatter { 25 26 public class FieldNames { FieldNames()27 public FieldNames () {} 28 29 public const string Version = "Version"; 30 public const string SerialNumber = "Serial number"; 31 public const string SignatureAlgorithm = "Signature algorithm"; 32 public const string Issuer = "Issuer"; 33 public const string ValidFrom = "Valid from"; 34 public const string ValidUntil = "Valid until"; 35 public const string Subject = "Subject"; 36 public const string PublicKey = "Public key"; 37 } 38 39 public class PropertyNames { PropertyNames()40 public PropertyNames () {} 41 42 public const string ThumbprintAlgorithm = "Thumbprint algorithm"; 43 public const string Thumbprint = "Thumbprint"; 44 } 45 46 public class Help { Help()47 public Help () {} 48 49 public const string IssuedBy = "This is the distinguished name (DN) of the certificate authority (CA) that issued this certificate."; 50 public const string IssuedTo = "This is the distinguished name (DN) of the entity (individual, device or organization) to whom the certificate was issued."; 51 public const string ValidFrom = "This certificate isn't valid before the specified date."; 52 public const string ValidUntil = "This certificate isn't valid after the specified date. This also means that the certificate authority (CA) won't publish the status of the certificate after this date."; 53 } 54 55 private const string untrustedRoot = "This root certificate isn't part of your trusted root store. Please read your documentation carefully before adding a new root certificate in your trusted store."; 56 private const string unknownCriticalExtension = "This certificate contains unknown critical extensions and shouldn't be used by applications that can't process those extensions."; 57 private const string noSignatureCheck = "The signature of the certificate can;t be verified without the issuer certificate."; 58 private const string noValidation = "No CRL, nor an OCSP responder, has been found to validate the status of the certificate."; 59 private const string unsupportedHash = "The {0} algorithm is unsupported by the .NET Framework. The certificate signature cannot be verified."; 60 61 private string thumbprintAlgorithm; 62 private X509Certificate x509; 63 private string status; 64 private string[] subjectAltName; 65 66 private static string defaultThumbprintAlgo; 67 private static Hashtable extensions; 68 CertificateFormatter()69 static CertificateFormatter () 70 { 71 IDictionary tb = (IDictionary) ConfigurationSettings.GetConfig ("Thumbprint"); 72 defaultThumbprintAlgo = ((tb != null) ? (string) tb ["Algorithm"] : "SHA1"); 73 74 extensions = new Hashtable (); 75 IDictionary exts = (IDictionary) ConfigurationSettings.GetConfig ("X509.Extensions"); 76 if (exts != null) { 77 foreach (DictionaryEntry ext in exts) 78 extensions.Add (ext.Key, ext.Value); 79 } 80 } 81 CreateExtensionFromOid(string oid, object[] args)82 private X509Extension CreateExtensionFromOid (string oid, object[] args) 83 { 84 try { 85 Type algoClass = null; 86 string algo = (string) extensions [oid]; 87 // do we have an entry 88 if (algo == null) 89 return (X509Extension) args [0]; 90 algoClass = Type.GetType (algo); 91 // call the constructor for the type 92 return (X509Extension) Activator.CreateInstance (algoClass, args); 93 } 94 catch { 95 // method doesn't throw any exception 96 return (X509Extension) args [0]; 97 } 98 } 99 CertificateFormatter(string filename)100 public CertificateFormatter (string filename) 101 { 102 byte[] data = null; 103 using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { 104 data = new byte [fs.Length]; 105 fs.Read (data, 0, data.Length); 106 fs.Close (); 107 } 108 109 if ((data != null) && (data.Length > 0)) { 110 X509Certificate x509 = null; 111 if (data [0] != 0x30) { 112 // it may be PEM encoded 113 data = FromPEM (data); 114 } 115 116 if (data [0] == 0x30) { 117 x509 = new X509Certificate (data); 118 if (x509 != null) { 119 Initialize (x509); 120 } 121 } 122 } 123 } 124 FromPEM(byte[] data)125 private byte[] FromPEM (byte[] data) 126 { 127 string pem = Encoding.ASCII.GetString (data); 128 int start = pem.IndexOf ("-----BEGIN CERTIFICATE-----"); 129 if (start < 0) 130 return null; 131 132 start += 27; // 27 being the -----BEGIN CERTIFICATE----- length 133 int end = pem.IndexOf ("-----END CERTIFICATE-----", start); 134 if (end < start) 135 return null; 136 137 string base64 = pem.Substring (start, (end - start)); 138 return Convert.FromBase64String (base64); 139 } 140 CertificateFormatter(X509Certificate cert)141 public CertificateFormatter (X509Certificate cert) 142 { 143 Initialize (cert); 144 } 145 Initialize(X509Certificate cert)146 internal void Initialize (X509Certificate cert) 147 { 148 x509 = cert; 149 thumbprintAlgorithm = defaultThumbprintAlgo; 150 try { 151 // preprocess some informations 152 foreach (X509Extension xe in x509.Extensions) { 153 if ((!extensions.ContainsKey (xe.Oid)) && (xe.Critical)) 154 status = unknownCriticalExtension; 155 if (xe.Oid == "2.5.29.17") { 156 SubjectAltNameExtension san = new SubjectAltNameExtension (xe); 157 subjectAltName = san.RFC822; 158 } 159 } 160 161 if (x509.IsSelfSigned) { 162 status = untrustedRoot; 163 } 164 } 165 catch (Exception e) { 166 status = e.ToString (); 167 } 168 } 169 170 public X509Certificate Certificate { 171 get { return x509; } 172 } 173 174 public string Status { 175 get { return status; } 176 } 177 GetExtension(int i)178 public X509Extension GetExtension (int i) 179 { 180 X509Extension xe = x509.Extensions [i]; 181 object[] extn = new object [1] { xe }; 182 return CreateExtensionFromOid (xe.Oid, extn); 183 } 184 Extension(int i, bool detailed)185 public string Extension (int i, bool detailed) 186 { 187 X509Extension xe = x509.Extensions [i]; 188 if (!detailed) 189 return Array2Word (xe.Value.Value); 190 return Extension2String (x509.Extensions[i].Value.Value); 191 } 192 DN(string dname, bool detailed)193 private string DN (string dname, bool detailed) 194 { 195 string[] a = dname.Split (','); 196 StringBuilder sb = new StringBuilder (); 197 198 if (detailed) { 199 foreach (string s in a) { 200 string s2 = s.Trim () + Environment.NewLine; 201 sb.Insert (0, s2.Replace ("=", " = ")); 202 } 203 } 204 else { 205 foreach (string s in a) { 206 string s2 = s.Trim (); 207 sb.Insert (0, s2.Substring (s2.IndexOf ("=") + 1) + ", "); 208 } 209 // must remove last ", " 210 sb.Remove (sb.Length - 2, 2); 211 } 212 213 return sb.ToString(); 214 } 215 Issuer(bool detailed)216 public string Issuer (bool detailed) 217 { 218 return DN (x509.IssuerName, detailed); 219 } 220 PublicKey(bool detailed)221 public string PublicKey (bool detailed) 222 { 223 if (detailed) 224 return Array2Word (x509.PublicKey); 225 226 if (x509.RSA != null) 227 return "RSA (" + x509.RSA.KeySize + " Bits)"; 228 else if (x509.DSA != null) 229 return "DSA (" + x509.DSA.KeySize + " Bits)"; 230 return "Unknown key type (unknown key size)"; 231 } 232 SerialNumber(bool detailed)233 public string SerialNumber (bool detailed) 234 { 235 byte[] sn = (byte[]) x509.SerialNumber.Clone (); 236 Array.Reverse (sn); 237 return CertificateFormatter.Array2Word (sn); 238 } 239 Subject(bool detailed)240 public string Subject (bool detailed) 241 { 242 return DN (x509.SubjectName, detailed); 243 } 244 SubjectAltName(bool detailed)245 public string SubjectAltName (bool detailed) 246 { 247 if ((subjectAltName == null) || (subjectAltName.Length < 1)) 248 return String.Empty; 249 if (!detailed) 250 return "mailto:" + subjectAltName [0]; 251 252 StringBuilder sb = new StringBuilder (); 253 foreach (string s in subjectAltName) { 254 sb.Append (s); 255 sb.Append (Environment.NewLine); 256 } 257 return sb.ToString (); 258 } 259 SignatureAlgorithm(bool detailed)260 public string SignatureAlgorithm (bool detailed) 261 { 262 string result = null; 263 264 switch (x509.SignatureAlgorithm) { 265 case "1.2.840.10040.4.3": 266 result = "sha1DSA"; 267 break; 268 case "1.2.840.113549.1.1.2": 269 result = "md2RSA"; 270 status = String.Format (unsupportedHash, "MD2"); 271 break; 272 case "1.2.840.113549.1.1.3": 273 result = "md4RSA"; 274 status = String.Format (unsupportedHash, "MD4"); 275 break; 276 case "1.2.840.113549.1.1.4": 277 result = "md5RSA"; 278 break; 279 case "1.2.840.113549.1.1.5": 280 result = "sha1RSA"; 281 break; 282 case "1.3.14.3.2.29": 283 result = "sha1WithRSASignature"; 284 break; 285 default: 286 result = x509.SignatureAlgorithm; 287 if (detailed) 288 return "unknown (" + result + ")"; 289 return result; 290 } 291 if (detailed) 292 result += " (" + x509.SignatureAlgorithm + ")"; 293 return result; 294 } 295 296 public string ThumbprintAlgorithm { 297 get { return thumbprintAlgorithm.ToLower (); } 298 set { thumbprintAlgorithm = value; } 299 } 300 301 public byte[] Thumbprint { 302 get { 303 HashAlgorithm ha = HashAlgorithm.Create (thumbprintAlgorithm); 304 return ha.ComputeHash (x509.RawData); 305 } 306 } 307 ValidFrom(bool detailed)308 public string ValidFrom (bool detailed) 309 { 310 return x509.ValidFrom.ToString (); 311 } 312 ValidUntil(bool detailed)313 public string ValidUntil (bool detailed) 314 { 315 return x509.ValidUntil.ToString (); 316 } 317 Version(bool detailed)318 public string Version (bool detailed) 319 { 320 return "V" + x509.Version; 321 } 322 OneLine(string input)323 static public string OneLine (string input) 324 { 325 // remove tabulation 326 string oneline = input.Replace ("\t", ""); 327 // remove new lines after : 328 oneline = oneline.Replace (":" + Environment.NewLine, ":"); 329 // remove ending new line (if present) 330 if (oneline.EndsWith (Environment.NewLine)) 331 oneline = oneline.Substring (0, oneline.Length - Environment.NewLine.Length); 332 // replace remaining new lines by comma + space 333 return oneline.Replace (Environment.NewLine, ", "); 334 } 335 Array2Word(byte[] array)336 static public string Array2Word (byte[] array) 337 { 338 StringBuilder sb = new StringBuilder (); 339 int x = 0; 340 while (x < array.Length) { 341 sb.Append (array [x].ToString ("X2")); 342 if (x % 2 == 1) 343 sb.Append (" "); 344 x++; 345 } 346 return sb.ToString (); 347 } 348 WriteLine(StringBuilder sb, byte[] extnValue, int n, int pos)349 static private void WriteLine (StringBuilder sb, byte[] extnValue, int n, int pos) 350 { 351 int p = pos; 352 StringBuilder preview = new StringBuilder (); 353 for (int j=0; j < 8; j++) { 354 if (j < n) { 355 sb.Append (extnValue [p++].ToString ("X2")); 356 sb.Append (" "); 357 } 358 else 359 sb.Append (" "); 360 } 361 sb.Append (" "); 362 p = pos; 363 for (int j=0; j < n; j++) { 364 byte b = extnValue [p++]; 365 if (b < 0x20) 366 sb.Append ("."); 367 else 368 sb.Append (Convert.ToChar (b)); 369 } 370 sb.Append (Environment.NewLine); 371 } 372 Extension2String(byte[] extnValue)373 static public string Extension2String (byte[] extnValue) 374 { 375 StringBuilder sb = new StringBuilder (); 376 int div = (extnValue.Length >> 3); 377 int rem = (extnValue.Length - (div << 3)); 378 int x = 0; 379 for (int i=0; i < div; i++) { 380 WriteLine (sb, extnValue, 8, x); 381 x += 8; 382 } 383 WriteLine (sb, extnValue, rem, x); 384 return sb.ToString (); 385 } 386 } 387 } 388