1 //
2 // makecert.cs: makecert clone tool
3 //
4 // Author:
5 //	Sebastien Pouliot <sebastien@ximian.com>
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
9 //
10 
11 using System;
12 using System.Collections;
13 using System.Globalization;
14 using System.IO;
15 using System.Reflection;
16 using System.Security.Cryptography;
17 
18 using Mono.Security.Authenticode;
19 using Mono.Security.X509;
20 using Mono.Security.X509.Extensions;
21 
22 [assembly: AssemblyTitle("Mono MakeCert")]
23 [assembly: AssemblyDescription("X.509 Certificate Builder")]
24 
25 namespace Mono.Tools {
26 
27 	class MakeCert {
28 
Header()29 		static private void Header ()
30 		{
31 			Console.WriteLine (new AssemblyInfo ().ToString ());
32 		}
33 
Help()34 		static private void Help ()
35 		{
36 			Console.WriteLine ("Usage: makecert [options] certificate{0}", Environment.NewLine);
37 			Console.WriteLine (" -# num{0}\tCertificate serial number", Environment.NewLine);
38 			Console.WriteLine (" -n dn{0}\tSubject Distinguished Name", Environment.NewLine);
39 			Console.WriteLine (" -in dn{0}\tIssuer Distinguished Name", Environment.NewLine);
40 			Console.WriteLine (" -r{0}\tCreate a self-signed (root) certificate", Environment.NewLine);
41 			Console.WriteLine (" -sv pkvfile{0}\tPrivate key file (.PVK) for the subject (created if missing)", Environment.NewLine);
42 			Console.WriteLine (" -iv pvkfile{0}\tPrivate key file (.PVK) for the issuer", Environment.NewLine);
43 			Console.WriteLine (" -ic certfile{0}\tExtract the issuer's name from the specified certificate", Environment.NewLine);
44 			Console.WriteLine (" -?{0}\thelp (display this help message)", Environment.NewLine);
45 			Console.WriteLine (" -!{0}\textended help (for advanced options)", Environment.NewLine);
46 		}
47 
ExtendedHelp()48 		static private void ExtendedHelp ()
49 		{
50 			Console.WriteLine ("Usage: makecert [options] certificate{0}", Environment.NewLine);
51 			Console.WriteLine (" -a hash\tSelect hash algorithm. Only MD5 and SHA1 (default) are supported.");
52 			Console.WriteLine (" -b date\tThe date since when the certificate is valid (notBefore).");
53 			Console.WriteLine (" -cy [authority|end]\tBasic constraints. Select Authority or End-Entity certificate.");
54 			Console.WriteLine (" -e date\tThe date until when the certificate is valid (notAfter).");
55 			Console.WriteLine (" -eku oid[,oid]\tAdd some extended key usage OID to the certificate.");
56 			Console.WriteLine (" -h number\tAdd a path length restriction to the certificate chain.");
57 			Console.WriteLine (" -in name\tTake the issuer's name from the specified parameter.");
58 			Console.WriteLine (" -m number\tCertificate validity period (in months).");
59 			Console.WriteLine (" -p12 pkcs12file password\tCreate a new PKCS#12 file with the specified password.");
60 			Console.WriteLine (" -?\thelp (display basic message)");
61 		}
62 
LoadCertificate(string filename)63 		static X509Certificate LoadCertificate (string filename)
64 		{
65 			FileStream fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read);
66 			byte[] rawcert = new byte [fs.Length];
67 			fs.Read (rawcert, 0, rawcert.Length);
68 			fs.Close ();
69 			return new X509Certificate (rawcert);
70 		}
71 
WriteCertificate(string filename, byte[] rawcert)72 		static void WriteCertificate (string filename, byte[] rawcert)
73 		{
74 			FileStream fs = File.Open (filename, FileMode.Create, FileAccess.Write);
75 			fs.Write (rawcert, 0, rawcert.Length);
76 			fs.Close ();
77 		}
78 
79 		static string MonoTestRootAgency = "<RSAKeyValue><Modulus>v/4nALBxCE+9JgEC0LnDUvKh6e96PwTpN4Rj+vWnqKT7IAp1iK/JjuqvAg6DQ2vTfv0dTlqffmHH51OyioprcT5nzxcSTsZb/9jcHScG0s3/FRIWnXeLk/fgm7mSYhjUaHNI0m1/NTTktipicjKxo71hGIg9qucCWnDum+Krh/k=</Modulus><Exponent>AQAB</Exponent><P>9jbKxMXEruW2CfZrzhxtull4O8P47+mNsEL+9gf9QsRO1jJ77C+jmzfU6zbzjf8+ViK+q62tCMdC1ZzulwdpXQ==</P><Q>x5+p198l1PkK0Ga2mRh0SIYSykENpY2aLXoyZD/iUpKYAvATm0/wvKNrE4dKJyPCA+y3hfTdgVag+SP9avvDTQ==</Q><DP>ISSjCvXsUfbOGG05eddN1gXxL2pj+jegQRfjpk7RAsnWKvNExzhqd5x+ZuNQyc6QH5wxun54inP4RTUI0P/IaQ==</DP><DQ>R815VQmR3RIbPqzDXzv5j6CSH6fYlcTiQRtkBsUnzhWmkd/y3XmamO+a8zJFjOCCx9CcjpVuGziivBqi65lVPQ==</DQ><InverseQ>iYiu0KwMWI/dyqN3RJYUzuuLj02/oTD1pYpwo2rvNCXU1Q5VscOeu2DpNg1gWqI+1RrRCsEoaTNzXB1xtKNlSw==</InverseQ><D>nIfh1LYF8fjRBgMdAH/zt9UKHWiaCnc+jXzq5tkR8HVSKTVdzitD8bl1JgAfFQD8VjSXiCJqluexy/B5SGrCXQ49c78NIQj0hD+J13Y8/E0fUbW1QYbhj6Ff7oHyhaYe1WOQfkp2t/h+llHOdt1HRf7bt7dUknYp7m8bQKGxoYE=</D></RSAKeyValue>";
80 
81 		static string defaultIssuer = "CN=Mono Test Root Agency";
82 		static string defaultSubject = "CN=Poupou's-Software-Factory";
83 
84 		[STAThread]
Main(string[] args)85 		static int Main (string[] args)
86 		{
87 			if (args.Length < 1) {
88 				Header ();
89 				Console.WriteLine ("ERROR: Missing output filename {0}", Environment.NewLine);
90 				Help ();
91 				return -1;
92 			}
93 
94 			string fileName = args [args.Length - 1];
95 
96 			// default values
97 			byte[] sn = Guid.NewGuid ().ToByteArray ();
98 			string subject = defaultSubject;
99 			string issuer = defaultIssuer;
100 			DateTime notBefore = DateTime.Now;
101 			DateTime notAfter = new DateTime (643445675990000000); // 12/31/2039 23:59:59Z
102 
103 			RSA issuerKey = (RSA)RSA.Create ();
104 			issuerKey.FromXmlString (MonoTestRootAgency);
105 			RSA subjectKey = (RSA)RSA.Create ();
106 
107 			bool selfSigned = false;
108 			string hashName = "SHA512";
109 
110 			CspParameters subjectParams = new CspParameters ();
111 			CspParameters issuerParams = new CspParameters ();
112 			BasicConstraintsExtension bce = null;
113 			ExtendedKeyUsageExtension eku = null;
114 			SubjectAltNameExtension alt = null;
115 			string p12file = null;
116 			string p12pwd = null;
117 			X509Certificate issuerCertificate = null;
118 
119 			Header();
120 			try {
121 				int i=0;
122 				while (i < args.Length) {
123 					switch (args [i++]) {
124 						// Basic options
125 						case "-#":
126 							// Serial Number
127 							sn = BitConverter.GetBytes (Convert.ToInt32 (args [i++]));
128 							break;
129 						case "-n":
130 							// Subject Distinguish Name
131 							subject = args [i++];
132 							break;
133 						case "-$":
134 							// (authenticode) commercial or individual
135 							// CRITICAL KeyUsageRestriction extension
136 							// hash algorithm
137 							string usageRestriction = args [i++].ToLower ();
138 							switch (usageRestriction) {
139 								case "commercial":
140 								case "individual":
141 									Console.WriteLine ("WARNING: Unsupported deprecated certification extension KeyUsageRestriction not included");
142 //									Console.WriteLine ("WARNING: ExtendedKeyUsage for codesigning has been included.");
143 									break;
144 								default:
145 									Console.WriteLine ("Unsupported restriction " + usageRestriction);
146 									return -1;
147 							}
148 							break;
149 						// Extended Options
150 						case "-a":
151 							// hash algorithm
152 							switch (args [i++].ToLower ()) {
153 								case "sha512":
154 									hashName = "SHA512";
155 									break;
156 								case "sha256":
157 									hashName = "SHA256";
158 									break;
159 								case "sha1":
160 									Console.WriteLine ("WARNING: SHA1 is not safe for this usage.");
161 									hashName = "SHA1";
162 									break;
163 								case "md5":
164 									Console.WriteLine ("WARNING: MD5 is not  safe for this usage.");
165 									hashName = "MD5";
166 									break;
167 								default:
168 									Console.WriteLine ("Unsupported hash algorithm");
169 									break;
170 							}
171 							break;
172 						case "-b":
173 							// Validity / notBefore
174 							notBefore = DateTime.Parse (args [i++] + " 23:59:59", CultureInfo.InvariantCulture);
175 							break;
176 						case "-cy":
177 							// basic constraints - autority or end-entity
178 							switch (args [i++].ToLower ()) {
179 								case "authority":
180 									if (bce == null)
181 										bce = new BasicConstraintsExtension ();
182 									bce.CertificateAuthority = true;
183 									break;
184 								case "end":
185 									// do not include extension
186 									bce = null;
187 									break;
188 								case "both":
189 									Console.WriteLine ("ERROR: No more supported in X.509");
190 									return -1;
191 								default:
192 									Console.WriteLine ("Unsupported certificate type");
193 									return -1;
194 							}
195 							break;
196 						case "-d":
197 							// CN private extension ?
198 							Console.WriteLine ("Unsupported option");
199 							break;
200 						case "-e":
201 							// Validity / notAfter
202 							notAfter = DateTime.Parse (args [i++] + " 23:59:59", CultureInfo.InvariantCulture);
203 							break;
204 						case "-eku":
205 							// extendedKeyUsage extension
206 							char[] sep = { ',' };
207 							string[] purposes = args [i++].Split (sep);
208 							if (eku == null)
209 								eku = new ExtendedKeyUsageExtension ();
210 							foreach (string purpose in purposes) {
211 								eku.KeyPurpose.Add (purpose);
212 							}
213 							break;
214 						case "-h":
215 							// pathLength (basicConstraints)
216 							// MS use an old basicConstrains (2.5.29.10) which
217 							// allows both CA and End-Entity. This is no
218 							// more supported with 2.5.29.19.
219 							if (bce == null) {
220 								bce = new BasicConstraintsExtension ();
221 								bce.CertificateAuthority = true;
222 							}
223 							bce.PathLenConstraint = Convert.ToInt32 (args [i++]);
224 							break;
225 						case "-alt":
226 							if (alt == null) {
227 								string [] dnsNames = File.ReadAllLines (args [i++]);
228 								alt = new SubjectAltNameExtension (null, dnsNames, null, null);
229 							}
230 							break;
231 						case "-ic":
232 							issuerCertificate = LoadCertificate (args [i++]);
233 							issuer = issuerCertificate.SubjectName;
234 							break;
235 						case "-in":
236 							issuer = args [i++];
237 							break;
238 						case "-iv":
239 							// TODO password
240 							PrivateKey pvk = PrivateKey.CreateFromFile (args [i++]);
241 							issuerKey = pvk.RSA;
242 							break;
243 						case "-l":
244 							// link (URL)
245 							// spcSpAgencyInfo private extension
246 							Console.WriteLine ("Unsupported option");
247 							break;
248 						case "-m":
249 							// validity period (in months)
250 							notAfter = notBefore.AddMonths (Convert.ToInt32 (args [i++]));
251 							break;
252 						case "-nscp":
253 							// Netscape's private extensions - NetscapeCertType
254 							// BasicContraints - End Entity
255 							Console.WriteLine ("Unsupported option");
256 							break;
257 						case "-r":
258 							selfSigned = true;
259 							break;
260 						case "-sc":
261 							// subject certificate ? renew ?
262 							Console.WriteLine ("Unsupported option");
263 							break;
264 						// Issuer CspParameters options
265 						case "-ik":
266 							issuerParams.KeyContainerName = args [i++];
267 							break;
268 						case "-iky":
269 							// select a key in the provider
270 							string ikn = args [i++].ToLower ();
271 							switch (ikn) {
272 								case "signature":
273 									issuerParams.KeyNumber = 0;
274 									break;
275 								case "exchange":
276 									issuerParams.KeyNumber = 1;
277 									break;
278 								default:
279 									issuerParams.KeyNumber = Convert.ToInt32 (ikn);
280 									break;
281 							}
282 							break;
283 						case "-ip":
284 							issuerParams.ProviderName = args [i++];
285 							break;
286 						case "-ir":
287 							switch (args [i++].ToLower ()) {
288 								case "localmachine":
289 									issuerParams.Flags = CspProviderFlags.UseMachineKeyStore;
290 									break;
291 								case "currentuser":
292 									issuerParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
293 									break;
294 								default:
295 									Console.WriteLine ("Unknown key store for issuer");
296 									return -1;
297 							}
298 							break;
299 						case "-is":
300 							Console.WriteLine ("Unsupported option");
301 							return -1;
302 						case "-iy":
303 							issuerParams.ProviderType = Convert.ToInt32 (args [i++]);
304 							break;
305 						// Subject CspParameters Options
306 						case "-sk":
307 							subjectParams.KeyContainerName = args [i++];
308 							break;
309 						case "-sky":
310 							// select a key in the provider
311 							string skn = args [i++].ToLower ();
312 							switch (skn) {
313 								case "signature":
314 									subjectParams.KeyNumber = 0;
315 									break;
316 								case "exchange":
317 									subjectParams.KeyNumber = 1;
318 									break;
319 								default:
320 									subjectParams.KeyNumber = Convert.ToInt32 (skn);
321 									break;
322 							}
323 							break;
324 						case "-sp":
325 							subjectParams.ProviderName = args [i++];
326 							break;
327 						case "-sr":
328 							switch (args [i++].ToLower ()) {
329 								case "localmachine":
330 									subjectParams.Flags = CspProviderFlags.UseMachineKeyStore;
331 									break;
332 								case "currentuser":
333 									subjectParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
334 									break;
335 								default:
336 									Console.WriteLine ("Unknown key store for subject");
337 									return -1;
338 							}
339 							break;
340 						case "-ss":
341 							Console.WriteLine ("Unsupported option");
342 							return -1;
343 						case "-sv":
344 							string pvkFile = args [i++];
345 							if (File.Exists (pvkFile)) {
346 								PrivateKey key = PrivateKey.CreateFromFile (pvkFile);
347 								subjectKey = key.RSA;
348 							}
349 							else {
350 								PrivateKey key = new PrivateKey ();
351 								key.RSA = subjectKey;
352 								key.Save (pvkFile);
353 							}
354 							break;
355 						case "-sy":
356 							subjectParams.ProviderType = Convert.ToInt32 (args [i++]);
357 							break;
358 						// Mono Specific Options
359 						case "-p12":
360 							p12file = args [i++];
361 							p12pwd = args [i++];
362 							break;
363 						// Other options
364 						case "-?":
365 							Help ();
366 							return 0;
367 						case "-!":
368 							ExtendedHelp ();
369 							return 0;
370 						default:
371 							if (i != args.Length) {
372 								Console.WriteLine ("ERROR: Unknown parameter");
373 								Help ();
374 								return -1;
375 							}
376 							break;
377 					}
378 				}
379 
380 				// serial number MUST be positive
381 				if ((sn [0] & 0x80) == 0x80)
382 					sn [0] -= 0x80;
383 
384 				if (selfSigned) {
385 					if (subject != defaultSubject) {
386 						issuer = subject;
387 						issuerKey = subjectKey;
388 					}
389 					else {
390 						subject = issuer;
391 						subjectKey = issuerKey;
392 					}
393 				}
394 
395 				if (subject == null)
396 					throw new Exception ("Missing Subject Name");
397 
398 				X509CertificateBuilder cb = new X509CertificateBuilder (3);
399 				cb.SerialNumber = sn;
400 				cb.IssuerName = issuer;
401 				cb.NotBefore = notBefore;
402 				cb.NotAfter = notAfter;
403 				cb.SubjectName = subject;
404 				cb.SubjectPublicKey = subjectKey;
405 				// extensions
406 				if (bce != null)
407 					cb.Extensions.Add (bce);
408 				if (eku != null)
409 					cb.Extensions.Add (eku);
410 				if (alt != null)
411 					cb.Extensions.Add (alt);
412 				// signature
413 				cb.Hash = hashName;
414 				byte[] rawcert = cb.Sign (issuerKey);
415 
416 				if (p12file == null) {
417 					WriteCertificate (fileName, rawcert);
418 				} else {
419 					PKCS12 p12 = new PKCS12 ();
420 					p12.Password = p12pwd;
421 
422 					ArrayList list = new ArrayList ();
423 					// we use a fixed array to avoid endianess issues
424 					// (in case some tools requires the ID to be 1).
425 					list.Add (new byte [4] { 1, 0, 0, 0 });
426 					Hashtable attributes = new Hashtable (1);
427 					attributes.Add (PKCS9.localKeyId, list);
428 
429 					p12.AddCertificate (new X509Certificate (rawcert), attributes);
430 					if (issuerCertificate != null)
431 						p12.AddCertificate (issuerCertificate);
432 					p12.AddPkcs8ShroudedKeyBag (subjectKey, attributes);
433 					p12.SaveToFile (p12file);
434 				}
435 				Console.WriteLine ("Success");
436 				return 0;
437 			}
438 			catch (Exception e) {
439 				Console.WriteLine ("ERROR: " + e.ToString ());
440 				Help ();
441 			}
442 			return 1;
443 		}
444 	}
445 }
446