2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35 * The KF class contains static methods to decode and encode algorithm
36 * parameters, public keys and private keys.
41 const string OID_RSA = "1.2.840.113549.1.1.1";
42 const string OID_RSA_OAEP = "1.2.840.113549.1.1.7";
43 const string OID_RSA_PSS = "1.2.840.113549.1.1.10";
44 const string OID_DSA = "1.2.840.10040.4.1";
45 const string OID_EC = "1.2.840.10045.2.1";
48 * Encode the private key. If 'pk8' is true, then this uses
49 * PKCS#8 format (unencrypted); otherwise, it uses the
50 * "internal" format that does not specifically identify the key
53 public static byte[] EncodePrivateKey(IPrivateKey sk, bool pk8)
55 RSAPrivateKey rk = sk as RSAPrivateKey;
57 DSAPrivateKey dk = sk as DSAPrivateKey;
59 ECPrivateKey ek = sk as ECPrivateKey;
61 AsnElt ark = AsnElt.Make(AsnElt.SEQUENCE,
62 AsnElt.MakeInteger(0),
63 AsnElt.MakeInteger(rk.N),
64 AsnElt.MakeInteger(rk.E),
65 AsnElt.MakeInteger(rk.D),
66 AsnElt.MakeInteger(rk.P),
67 AsnElt.MakeInteger(rk.Q),
68 AsnElt.MakeInteger(rk.DP),
69 AsnElt.MakeInteger(rk.DQ),
70 AsnElt.MakeInteger(rk.IQ));
71 byte[] enc = ark.Encode();
73 AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
74 AsnElt.MakeInteger(0),
75 AsnElt.Make(AsnElt.SEQUENCE,
76 AsnElt.MakeOID(OID_RSA),
78 AsnElt.MakeBlob(enc));
83 } else if (dk != null) {
85 AsnElt adx = AsnElt.MakeInteger(dk.X);
86 AsnElt adp = AsnElt.Make(AsnElt.SEQUENCE,
87 AsnElt.MakeInteger(dk.P),
88 AsnElt.MakeInteger(dk.Q),
89 AsnElt.MakeInteger(dk.G));
90 AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
91 AsnElt.MakeInteger(0),
92 AsnElt.Make(AsnElt.SEQUENCE,
93 AsnElt.MakeOID(OID_DSA),
95 AsnElt.MakeBlob(adx.Encode()));
98 AsnElt adk = AsnElt.Make(AsnElt.SEQUENCE,
99 AsnElt.MakeInteger(0),
100 AsnElt.MakeInteger(dk.P),
101 AsnElt.MakeInteger(dk.Q),
102 AsnElt.MakeInteger(dk.G),
103 AsnElt.MakeInteger(dk.PublicKey.Y),
104 AsnElt.MakeInteger(dk.X));
108 } else if (ek != null) {
110 * The ECPrivateKey class guarantees that the
111 * private key X is already encoded with the same
112 * length as the subgroup order.
113 * The ECPublicKey class provides the public key
114 * as an already encoded point.
116 AsnElt acc = AsnElt.MakeOID(CurveToOID(ek.Curve));
117 AsnElt apv = AsnElt.MakeExplicit(AsnElt.CONTEXT, 1,
118 AsnElt.MakeBitString(ek.PublicKey.Pub));
120 AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
121 AsnElt.MakeInteger(1),
122 AsnElt.MakeBlob(ek.X),
124 AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
125 AsnElt.MakeInteger(0),
126 AsnElt.Make(AsnElt.SEQUENCE,
127 AsnElt.MakeOID(OID_EC),
129 AsnElt.MakeBlob(aek.Encode()));
130 return apk8.Encode();
132 AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
133 AsnElt.MakeInteger(1),
134 AsnElt.MakeBlob(ek.X),
136 AsnElt.CONTEXT, 0, acc),
142 throw new NullReferenceException();
144 throw new ArgumentException("Cannot encode "
145 + sk.AlgorithmName + " private key");
150 * Encode the private key into a PEM object. If 'pk8' is true,
151 * then unencrypted PKCS#8 format is used, and the PEM header
152 * is "BEGIN PRIVATE KEY"; otherwise, the "internal" private key
153 * format is used, and the PEM header identifies the key type.
155 * If 'crlf' is true, then PEM lines end with CR+LF; otherwise,
156 * they end with LF only.
158 public static string EncodePrivateKeyPEM(IPrivateKey sk,
161 byte[] enc = EncodePrivateKey(sk, pk8);
164 objType = "PRIVATE KEY";
166 objType = sk.AlgorithmName + " PRIVATE KEY";
168 return ToPEM(objType, enc, crlf ? "\r\n" : "\n");
172 * Decode the provided private key. This method accepts both
173 * PKCS#8 and the "internal" format; the source object may be
174 * raw DER, Base64-encoded DER, or PEM. The key type is
175 * automatically detected.
177 public static IPrivateKey DecodePrivateKey(byte[] enc)
180 enc = AsnIO.FindBER(enc, false, out pemType);
182 throw new AsnException("Not an encoded object");
184 AsnElt ak = AsnElt.Decode(enc);
185 ak.CheckConstructed();
186 if (pemType != null) {
188 case "RSA PRIVATE KEY":
189 return DecodePrivateKeyRSA(ak);
191 case "DSA PRIVATE KEY":
192 return DecodePrivateKeyDSA(ak);
194 case "EC PRIVATE KEY":
195 return DecodePrivateKeyEC(ak);
197 return DecodePrivateKeyPKCS8(ak);
199 throw new AsnException(
200 "Unknown PEM object: " + pemType);
203 if (ak.Sub.Length == 3
204 && ak.GetSub(0).TagValue == AsnElt.INTEGER
205 && ak.GetSub(1).TagValue == AsnElt.SEQUENCE
206 && ak.GetSub(2).TagValue == AsnElt.OCTET_STRING)
208 return DecodePrivateKeyPKCS8(ak);
210 if (ak.Sub.Length >= 9) {
211 bool mayBeRSA = true;
212 for (int i = 0; i < 9; i ++) {
213 if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
219 return DecodePrivateKeyRSA(ak);
223 if (ak.Sub.Length >= 6) {
224 bool mayBeDSA = true;
225 for (int i = 0; i < 6; i ++) {
226 if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
232 return DecodePrivateKeyDSA(ak);
236 if (ak.Sub.Length >= 2
237 && ak.GetSub(0).TagValue == AsnElt.INTEGER
238 && ak.GetSub(1).TagValue == AsnElt.OCTET_STRING)
240 return DecodePrivateKeyEC(ak);
242 throw new AsnException("Unrecognized private key format");
245 static RSAPrivateKey DecodePrivateKeyRSA(AsnElt ak)
247 ak.CheckNumSubMin(9);
248 ak.GetSub(0).CheckTag(AsnElt.INTEGER);
249 long kt = ak.GetSub(0).GetInteger();
251 throw new AsnException(
252 "Unsupported RSA key type: " + kt);
255 return new RSAPrivateKey(
256 GetPositiveInteger(ak.GetSub(1)),
257 GetPositiveInteger(ak.GetSub(2)),
258 GetPositiveInteger(ak.GetSub(3)),
259 GetPositiveInteger(ak.GetSub(4)),
260 GetPositiveInteger(ak.GetSub(5)),
261 GetPositiveInteger(ak.GetSub(6)),
262 GetPositiveInteger(ak.GetSub(7)),
263 GetPositiveInteger(ak.GetSub(8)));
267 static DSAPrivateKey DecodePrivateKeyDSA(AsnElt ak)
269 ak.CheckNumSubMin(6);
270 for (int i = 0; i < 6; i ++) {
271 ak.GetSub(i).CheckTag(AsnElt.INTEGER);
273 long kt = ak.GetSub(0).GetInteger();
275 throw new AsnException(
276 "Unsupported DSA key type: " + kt);
278 DSAPrivateKey dsk = new DSAPrivateKey(
279 GetPositiveInteger(ak.GetSub(1)),
280 GetPositiveInteger(ak.GetSub(2)),
281 GetPositiveInteger(ak.GetSub(3)),
282 GetPositiveInteger(ak.GetSub(5)));
283 DSAPublicKey dpk = dsk.PublicKey;
284 if (BigInt.Compare(dpk.Y,
285 GetPositiveInteger(ak.GetSub(4))) != 0)
287 throw new CryptoException(
288 "DSA key pair public/private mismatch");
294 static ECPrivateKey DecodePrivateKeyEC(AsnElt ak)
296 return DecodePrivateKeyEC(ak, null);
299 static ECPrivateKey DecodePrivateKeyEC(AsnElt ak, ECCurve curve)
301 ak.CheckNumSubMin(2);
302 ak.GetSub(0).CheckTag(AsnElt.INTEGER);
303 ak.GetSub(1).CheckTag(AsnElt.OCTET_STRING);
304 long kt = ak.GetSub(0).GetInteger();
306 throw new AsnException(
307 "Unsupported EC key type: " + kt);
309 byte[] x = ak.GetSub(1).CopyValue();
311 int n = ak.Sub.Length;
314 AsnElt acc = ak.GetSub(p);
315 if (acc.TagClass == AsnElt.CONTEXT
316 && acc.TagValue == 0)
320 ECCurve curve2 = DecodeCurve(acc);
323 * Here, we support only named curves.
329 } else if (!curve.Equals(curve2)) {
330 throw new AsnException(string.Format(
332 + " specification ({0} / {1})",
333 curve.Name, curve2.Name));
340 AsnElt acc = ak.GetSub(p);
341 if (acc.TagClass == AsnElt.CONTEXT
342 && acc.TagValue == 1)
346 acc.CheckTag(AsnElt.BIT_STRING);
347 pub = acc.GetBitString();
352 throw new AsnException("No curve specified for EC key");
354 ECPrivateKey esk = new ECPrivateKey(curve, x);
356 ECPublicKey epk = new ECPublicKey(curve, pub);
357 if (!epk.Equals(esk.PublicKey)) {
358 throw new CryptoException(
359 "EC key pair public/private mismatch");
365 static ECCurve DecodeCurve(AsnElt acc)
368 * We support only named curves for now. PKIX does not
369 * want to see any other kind of curve anyway (see RFC
372 acc.CheckTag(AsnElt.OBJECT_IDENTIFIER);
373 string oid = acc.GetOID();
374 return OIDToCurve(oid);
377 static IPrivateKey DecodePrivateKeyPKCS8(AsnElt ak)
380 ak.GetSub(0).CheckTag(AsnElt.INTEGER);
381 long v = ak.GetSub(0).GetInteger();
383 throw new AsnException(
384 "Unsupported PKCS#8 version: " + v);
386 AsnElt aai = ak.GetSub(1);
387 aai.CheckTag(AsnElt.SEQUENCE);
388 aai.CheckNumSubMin(1);
389 aai.CheckNumSubMin(2);
390 aai.GetSub(0).CheckTag(AsnElt.OBJECT_IDENTIFIER);
391 string oid = aai.GetSub(0).GetOID();
392 ak.GetSub(2).CheckTag(AsnElt.OCTET_STRING);
393 byte[] rawKey = ak.GetSub(2).CopyValue();
394 AsnElt ark = AsnElt.Decode(rawKey);
400 return DecodePrivateKeyRSA(ark);
403 return DecodePrivateKeyDSA(ark);
407 * For elliptic curves, the parameters may
408 * include the curve specification.
410 ECCurve curve = (aai.Sub.Length == 2)
411 ? DecodeCurve(aai.GetSub(1)) : null;
412 return DecodePrivateKeyEC(ark, curve);
414 throw new AsnException(
415 "Unknown PKCS#8 key type: " + oid);
420 * Encode a public key as a SubjectPublicKeyInfo structure.
422 public static AsnElt EncodePublicKey(IPublicKey pk)
424 RSAPublicKey rk = pk as RSAPublicKey;
426 DSAPublicKey dk = pk as DSAPublicKey;
428 ECPublicKey ek = pk as ECPublicKey;
435 pkv = AsnElt.Make(AsnElt.SEQUENCE,
436 AsnElt.MakeInteger(rk.Modulus),
437 AsnElt.MakeInteger(rk.Exponent)).Encode();
439 } else if (dk != null) {
441 app = AsnElt.Make(AsnElt.SEQUENCE,
442 AsnElt.MakeInteger(dk.P),
443 AsnElt.MakeInteger(dk.Q),
444 AsnElt.MakeInteger(dk.G));
445 pkv = AsnElt.MakeInteger(dk.Y).Encode();
447 } else if (ek != null) {
449 app = AsnElt.MakeOID(CurveToOID(ek.Curve));
452 throw new ArgumentException(
453 "Cannot encode key type: " + pk.AlgorithmName);
457 ai = AsnElt.Make(AsnElt.SEQUENCE,
458 AsnElt.MakeOID(oid));
460 ai = AsnElt.Make(AsnElt.SEQUENCE,
464 return AsnElt.Make(AsnElt.SEQUENCE,
466 AsnElt.MakeBitString(pkv));
470 * Decode a public key (SubjectPublicKeyInfo).
472 public static IPublicKey DecodePublicKey(byte[] spki)
474 string pemType = null;
475 spki = AsnIO.FindBER(spki, false, out pemType);
477 throw new AsnException("Not an encoded object");
479 return DecodePublicKey(AsnElt.Decode(spki));
483 * Decode a public key (SubjectPublicKeyInfo).
485 public static IPublicKey DecodePublicKey(AsnElt ak)
488 AlgorithmIdentifier ai = new AlgorithmIdentifier(ak.GetSub(0));
489 AsnElt abs = ak.GetSub(1);
490 abs.CheckTag(AsnElt.BIT_STRING);
491 byte[] pub = abs.GetBitString();
496 return DecodePublicKeyRSA(pub);
499 return DecodePublicKeyDSA(pub);
503 * For elliptic curves, the parameters should
504 * include the curve specification.
506 AsnElt ap = ai.Parameters;
508 throw new AsnException("No curve specified"
509 + " for EC public key");
511 if (ap.TagClass != AsnElt.UNIVERSAL
512 || ap.TagValue != AsnElt.OBJECT_IDENTIFIER)
514 throw new AsnException("Unsupported type"
515 + " of curve specification");
517 return new ECPublicKey(OIDToCurve(ap.GetOID()), pub);
519 throw new AsnException(
520 "Unknown public key type: " + ai.OID);
524 static IPublicKey DecodePublicKeyRSA(byte[] pub)
526 AsnElt ae = AsnElt.Decode(pub);
527 ae.CheckTag(AsnElt.SEQUENCE);
529 byte[] n = GetPositiveInteger(ae.GetSub(0));
530 byte[] e = GetPositiveInteger(ae.GetSub(1));
531 return new RSAPublicKey(n, e);
534 static string CurveToOID(ECCurve curve)
536 switch (curve.Name) {
538 return "1.2.840.10045.3.1.1";
540 return "1.3.132.0.33";
542 return "1.2.840.10045.3.1.7";
544 return "1.3.132.0.34";
546 return "1.3.132.0.35";
548 throw new ArgumentException(string.Format(
549 "No known OID for curve '{0}'", curve.Name));
552 static ECCurve OIDToCurve(string oid)
556 case "1.2.840.10045.3.1.1":
561 case "1.2.840.10045.3.1.7":
568 throw new ArgumentException(string.Format(
569 "No known curve for OID {0}", oid));
572 static byte[] GetPositiveInteger(AsnElt ae)
574 ae.CheckTag(AsnElt.INTEGER);
575 byte[] x = ae.CopyValue();
577 throw new AsnException("Invalid integer (empty)");
580 throw new AsnException("Invalid integer (negative)");
585 static int Dec16be(byte[] buf, int off)
587 return (buf[off] << 8) + buf[off + 1];
590 static int Dec24be(byte[] buf, int off)
592 return (buf[off] << 16) + (buf[off + 1] << 8) + buf[off + 2];
595 const string B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
596 + "abcdefghijklmnopqrstuvwxyz0123456789+/";
598 public static string ToBase64(byte[] buf, int off, int len)
600 char[] tc = new char[((len + 2) / 3) << 2];
601 for (int i = 0, j = 0; i < len; i += 3) {
602 if ((i + 3) <= len) {
603 int x = Dec24be(buf, off + i);
604 tc[j ++] = B64[x >> 18];
605 tc[j ++] = B64[(x >> 12) & 0x3F];
606 tc[j ++] = B64[(x >> 6) & 0x3F];
607 tc[j ++] = B64[x & 0x3F];
608 } else if ((i + 2) == len) {
609 int x = Dec16be(buf, off + i);
610 tc[j ++] = B64[x >> 10];
611 tc[j ++] = B64[(x >> 4) & 0x3F];
612 tc[j ++] = B64[(x << 2) & 0x3F];
614 } else if ((i + 1) == len) {
615 int x = buf[off + i];
616 tc[j ++] = B64[(x >> 2) & 0x3F];
617 tc[j ++] = B64[(x << 4) & 0x3F];
622 return new string(tc);
625 public static void WritePEM(TextWriter w, string objType, byte[] buf)
627 w.WriteLine("-----BEGIN {0}-----", objType.ToUpperInvariant());
629 for (int i = 0; i < n; i += 57) {
630 int len = Math.Min(57, n - i);
631 w.WriteLine(ToBase64(buf, i, len));
633 w.WriteLine("-----END {0}-----", objType.ToUpperInvariant());
636 public static string ToPEM(string objType, byte[] buf)
638 return ToPEM(objType, buf, "\n");
641 public static string ToPEM(string objType, byte[] buf, string nl)
643 StringWriter w = new StringWriter();
645 WritePEM(w, objType, buf);