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