OpenSSLで生成されたpem形式のx 509証明書を読み込むための完全なクラス

74326 ワード

  1     internal static class CcbRsaHelper
  2     {
  3         private const string Begin = "-----BEGIN ";
  4         private const string End = "-----END ";
  5         private const string Private = "PRIVATE KEY";
  6 
  7         /// <summary>Imports PEM formatted key or certificate into crypto provider</summary>
  8         /// <param name="data">Content of PEM-formatted object.</param>
  9         /// <returns>Crypto provider, defined by given argument.</returns>
 10         internal static RSACryptoServiceProvider FromPem(string data)
 11         {
 12             return FromPem(data, null);
 13         }
 14 
 15         /// <summary>Imports PEM formatted key or certificate into crypto provider</summary>
 16         /// <param name="data">Content of PEM-formatted object.</param>
 17         /// <param name="passKey">Passkey for PEM-formatted object.</param>
 18         /// <returns>Crypto provider, defined by given argument.</returns>
 19         internal static RSACryptoServiceProvider FromPem(string data, string passKey)
 20         {
 21             using (var reader = new StringReader(data))
 22             {
 23                 var line = reader.ReadLine();
 24                 if (line.IsEmpty() || !line.StartsWith(Begin))
 25                 {
 26                     throw new ArgumentException("This is not valid PEM format", "data", new FormatException("PEM start identifier is invalid or not found."));
 27                 }
 28                 line = line.Substring(Begin.Length);
 29                 var idx = line.IndexOf('-');
 30                 if (idx <= 0)
 31                 {
 32                     throw new ArgumentException("This is not valid PEM format", "data", new FormatException("PEM start identifier is invalid or not found."));
 33                 }
 34                 var type = line.Before(idx);
 35                 return LoadPem(reader, type, passKey);
 36             }
 37         }
 38 
 39         internal static RSAParameters FromPemPublicKey(string pemFileConent)
 40         {
 41             if (string.IsNullOrEmpty(pemFileConent))
 42             {
 43                 throw new ArgumentNullException("pemFileConent", "This arg cann't be empty.");
 44             }
 45 
 46             pemFileConent = pemFileConent.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "").Replace("
", "").Replace("\r", ""); 47 var keyData = Convert.FromBase64String(pemFileConent); 48 var keySize1024 = (keyData.Length == 162); 49 var keySize2048 = (keyData.Length == 294); 50 if (!(keySize1024 || keySize2048)) 51 { 52 throw new ArgumentException("pem file content is incorrect, Only support the key size is 1024 or 2048"); 53 } 54 55 var pemModulus = (keySize1024 ? new byte[128] : new byte[256]); 56 var pemPublicExponent = new byte[3]; 57 Array.Copy(keyData, (keySize1024 ? 29 : 33), pemModulus, 0, (keySize1024 ? 128 : 256)); 58 Array.Copy(keyData, (keySize1024 ? 159 : 291), pemPublicExponent, 0, 3); 59 var para = new RSAParameters {Modulus = pemModulus, Exponent = pemPublicExponent}; 60 return para; 61 } 62 63 private static RSACryptoServiceProvider LoadPem(TextReader reader, string type, string passkey) 64 { 65 var end = End + type; 66 var headers = new PemHeaders(); 67 string line; 68 var body = new StringBuilder(); 69 while ((line = reader.ReadLine()) != null && line.IndexOf(end, StringComparison.Ordinal) == -1) 70 { 71 var d = line.IndexOf(':'); 72 if (d >= 0) 73 { 74 // header 75 var n = line.Substring(0, d).Trim(); 76 if (n.StartsWith("X-")) n = n.Substring(2); 77 var v = line.After(d).Trim(); 78 if (!headers.ContainsKey(n)) 79 { 80 headers.Add(n, v); 81 } 82 else 83 { 84 throw new FormatException("Duplicate header {0} in PEM data.".Substitute(n)); 85 } 86 } 87 else 88 { 89 // body 90 body.Append(line); 91 } 92 } 93 if (body.Length%4 != 0) 94 { 95 throw new FormatException("PEM data is invalid or truncated."); 96 } 97 98 return CreatePem(type, headers, Convert.FromBase64String(body.ToString()), passkey); 99 } 100 101 private static RSACryptoServiceProvider CreatePem(string type, PemHeaders headers, byte[] body, string passkey) 102 { 103 if (type.EndsWith(Private)) 104 { 105 return FromPrivateKey(type, headers, body, passkey); 106 } 107 throw new NotSupportedException("Import of {0} is not supported. Only RSA private key import is supported.".Substitute(type)); 108 } 109 110 111 private static RSACryptoServiceProvider FromPrivateKey(string type, PemHeaders headers, byte[] body, string passkey) 112 { 113 if (type == null) throw new ArgumentNullException("type"); 114 var pType = headers.TryGet("Proc-Type"); 115 if (pType != "4,ENCRYPTED") return null; 116 if (passkey.IsEmpty()) 117 { 118 throw new ArgumentException("Passkey is mandatory for encrypted PEM object"); 119 } 120 121 var dek = headers.TryGet("DEK-Info"); 122 var tkz = dek.Split(','); 123 if (tkz.Length > 1) 124 { 125 var alg = new Alg(tkz[0]); 126 var saltLen = tkz[1].Length; 127 var salt = new byte[saltLen/2]; 128 for (var i = 0; i < saltLen/2; i++) 129 { 130 var pair = tkz[1].Substring(2*i, 2); 131 salt[i] = Byte.Parse(pair, NumberStyles.AllowHexSpecifier); 132 } 133 134 body = DecodePem(body, passkey, alg, salt); 135 if (body != null) 136 { 137 return DecodeRsaPrivateKey(body); 138 } 139 } 140 else 141 { 142 throw new FormatException("DEK information is invalid or truncated."); 143 } 144 145 return null; 146 } 147 148 private static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] body) 149 { 150 using (var ms = new MemoryStream(body)) 151 { 152 using (var reader = new BinaryReader(ms)) 153 { 154 try 155 { 156 var tb = reader.ReadUInt16(); // LE: x30 x81 157 switch (tb) 158 { 159 case 0x8130: 160 reader.ReadByte(); // fw 1 161 break; 162 case 0x8230: 163 reader.ReadInt16(); // fw 2 164 break; 165 default: 166 return null; 167 } 168 169 tb = reader.ReadUInt16(); // version 170 if (tb != 0x0102) 171 { 172 return null; 173 } 174 if (reader.ReadByte() != 0x00) 175 { 176 return null; 177 } 178 179 var modulus = ReadInt(reader); 180 var e = ReadInt(reader); 181 var d = ReadInt(reader); 182 var p = ReadInt(reader); 183 var q = ReadInt(reader); 184 var dp = ReadInt(reader); 185 var dq = ReadInt(reader); 186 var iq = ReadInt(reader); 187 188 var result = new RSACryptoServiceProvider(); 189 var param = new RSAParameters 190 { 191 Modulus = modulus, 192 Exponent = e, 193 D = d, 194 P = p, 195 Q = q, 196 DP = dp, 197 DQ = dq, 198 InverseQ = iq 199 }; 200 result.ImportParameters(param); 201 return result; 202 } 203 finally 204 { 205 reader.Close(); 206 } 207 } 208 } 209 } 210 211 private static readonly Func<BinaryReader, byte[]> ReadInt = r => 212 { 213 var s = GetIntSize(r); 214 return r.ReadBytes(s); 215 }; 216 217 private static readonly Func<BinaryReader, int> GetIntSize = r => 218 { 219 int c; 220 var b = r.ReadByte(); 221 if (b != 0x02) 222 { 223 //int 224 return 0; 225 } 226 b = r.ReadByte(); 227 228 switch (b) 229 { 230 case 0x81: 231 c = r.ReadByte(); //size 232 break; 233 case 0x82: 234 var hb = r.ReadByte(); 235 var lb = r.ReadByte(); 236 byte[] m = {lb, hb, 0x00, 0x00}; 237 c = BitConverter.ToInt32(m, 0); 238 break; 239 default: 240 c = b; //got size 241 break; 242 } 243 244 while (r.ReadByte() == 0x00) 245 { 246 //remove high zero 247 c -= 1; 248 } 249 r.BaseStream.Seek(-1, SeekOrigin.Current); // last byte is not zero, go back; 250 return c; 251 }; 252 253 private static byte[] DecodePem(byte[] body, string passkey, Alg alg, byte[] salt) 254 { 255 if (alg == null) throw new ArgumentNullException("alg"); 256 if (alg.AlgBase != Alg.BaseAlg.DES_EDE3 && alg.AlgMode != Alg.Mode.CBC) 257 { 258 throw new NotSupportedException("Only 3DES-CBC keys are supported."); 259 } 260 var des = Get3DesKey(salt, passkey); 261 if (des == null) 262 { 263 throw new ApplicationException("Unable to calculate 3DES key for decryption."); 264 } 265 var rsa = DecryptRsaKey(body, des, salt); 266 if (rsa == null) 267 { 268 throw new ApplicationException("Unable to decrypt RSA private key."); 269 } 270 return rsa; 271 } 272 273 private static byte[] DecryptRsaKey(byte[] body, byte[] desKey, byte[] iv) 274 { 275 byte[] result; 276 using (var stream = new MemoryStream()) 277 { 278 var alg = TripleDES.Create(); 279 alg.Key = desKey; 280 alg.IV = iv; 281 using (var cs = new CryptoStream(stream, alg.CreateDecryptor(), CryptoStreamMode.Write)) 282 { 283 cs.Write(body, 0, body.Length); 284 cs.Close(); 285 } 286 result = stream.ToArray(); 287 } 288 return result; 289 } 290 291 private static byte[] Get3DesKey(byte[] salt, string passkey) 292 { 293 const int hashlength = 16; 294 const int m = 2; // 2 iterations for at least 24 bytes 295 const int c = 1; // 1 hash for Open SSL 296 var k = new byte[hashlength*m]; 297 298 var pk = Encoding.ASCII.GetBytes(passkey); 299 var data = new byte[salt.Length + pk.Length]; 300 Array.Copy(pk, data, pk.Length); 301 Array.Copy(salt, 0, data, pk.Length, salt.Length); 302 var md5 = new MD5CryptoServiceProvider(); 303 byte[] result = null; 304 var hash = new byte[hashlength + data.Length]; 305 306 for (var i = 0; i < m; i++) 307 { 308 if (i == 0) 309 { 310 result = data; 311 } 312 else 313 { 314 Array.Copy(result, hash, result.Length); 315 Array.Copy(data, 0, hash, result.Length, data.Length); 316 result = hash; 317 } 318 319 for (var j = 0; j < c; j++) 320 { 321 result = md5.ComputeHash(result); 322 } 323 Array.Copy(result, 0, k, i*hashlength, result.Length); 324 } 325 var dk = new byte[24]; //final key 326 Array.Copy(k, dk, dk.Length); 327 return dk; 328 } 329 330 private class PemHeaders : Dictionary<string, string> 331 { 332 } 333 334 private sealed class Alg 335 { 336 public Alg(string alg) 337 { 338 AlgMode = Mode.ECB; 339 switch (alg.Trim()) 340 { 341 //TK: DES-EDE based algorithms come only with ECB mode. 342 case "DES-EDE": 343 AlgBase = BaseAlg.DES_EDE; 344 return; 345 case "DES-EDE3": 346 AlgBase = BaseAlg.DES_EDE3; 347 return; 348 default: 349 var p = alg.LastIndexOf('-'); 350 if (p >= 0) 351 { 352 AlgBase = (BaseAlg) _parseVal(typeof (BaseAlg), alg.Before(p)); 353 AlgMode = (Mode) _parseVal(typeof (Mode), alg.After(p)); 354 return; 355 } 356 break; 357 } 358 throw new ArgumentException("Unknown DEK algorithm '{0}'", alg); 359 } 360 361 public BaseAlg AlgBase { get; private set; } 362 363 public Mode AlgMode { get; private set; } 364 365 private readonly Func<Type, string, Enum> _parseVal = (t, s) => 366 { 367 s = s.Replace('-', '_'); 368 return (Enum) Enum.Parse(t, s); 369 }; 370 371 public enum BaseAlg 372 { 373 DES_EDE, 374 DES_EDE3 375 }; 376 377 public enum Mode 378 { 379 CBC, 380 ECB 381 }; 382 } 383 } 384 385 internal static class Helper 386 { 387 public static bool IsEmpty(this string value) 388 { 389 return String.IsNullOrWhiteSpace(value); 390 } 391 392 public static string Before(this string value, int end) 393 { 394 return (end == 0 ? String.Empty : value.Between(0, end - 1)); 395 } 396 397 public static string After(this string value, int start) 398 { 399 return value.Between(start + 1, Int32.MaxValue); 400 } 401 402 public static string Between(this string value, int start, int end) 403 { 404 var len = (String.IsNullOrEmpty(value) ? 0 : value.Length); 405 if (start < 0) start += len; 406 if (end < 0) end += len; 407 if (len == 0 || start > len - 1 || end < start) 408 { 409 return String.Empty; 410 } 411 if (start < 0) start = 0; 412 if (end >= len) end = len - 1; 413 return value.Substring(start, end - start + 1); 414 } 415 416 public static string Substitute(this string format, params object[] args) 417 { 418 var value = String.Empty; 419 if (String.IsNullOrEmpty(format)) return value; 420 if (args.Length == 0) return format; 421 try 422 { 423 return String.Format(format, args); 424 } 425 catch (FormatException) 426 { 427 return format; 428 } 429 catch 430 { 431 return "***"; 432 } 433 } 434 435 public static TV TryGet<TK, TV>(this Dictionary<TK, TV> dictionary, TK key) 436 { 437 return dictionary.TryGet(key, default(TV)); 438 } 439 440 public static TV TryGet<TK, TV>(this Dictionary<TK, TV> dictionary, TK key, TV defaultValue) 441 { 442 if (dictionary != null && dictionary.ContainsKey(key)) 443 { 444 return dictionary[key]; 445 } 446 return defaultValue; 447 } 448 }