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