From 9e5c88451db965bc7aec2095c9898e1a4c91b9dc Mon Sep 17 00:00:00 2001 From: psoares33 Date: Fri, 3 Apr 2009 17:34:37 +0000 Subject: [PATCH] BouncyCastle update. git-svn-id: svn://svn.code.sf.net/p/itextsharp/code/trunk@19 820d3149-562b-4f88-9aa4-a8e61a3485cf --- .../iTextSharp/text/pdf/codec/PngImage.cs | 1763 ++++++------ src/core/itextsharp.csproj | 192 +- src/core/srcbc/asn1/ASN1StreamParser.cs | 8 + src/core/srcbc/asn1/Asn1InputStream.cs | 8 + src/core/srcbc/asn1/BerApplicationSpecific.cs | 15 + .../asn1/BerApplicationSpecificParser.cs | 29 + src/core/srcbc/asn1/DerApplicationSpecific.cs | 84 +- .../asn1/IAsn1ApplicationSpecificParser.cs | 10 + src/core/srcbc/asn1/cms/AuthenticatedData.cs | 273 ++ .../srcbc/asn1/cms/AuthenticatedDataParser.cs | 164 ++ .../srcbc/asn1/eac/EACObjectIdentifiers.cs | 48 + src/core/srcbc/asn1/nist/NISTNamedCurves.cs | 3 +- .../srcbc/asn1/pkcs/PKCSObjectIdentifiers.cs | 1 + src/core/srcbc/asn1/util/Asn1Dump.cs | 141 +- src/core/srcbc/asn1/x509/ExtendedKeyUsage.cs | 11 + .../asn1/x509/V3TBSCertificateGenerator.cs | 24 + src/core/srcbc/asn1/x509/X509Name.cs | 2150 +++++++------- .../srcbc/asn1/x509/X509ObjectIdentifiers.cs | 3 + src/core/srcbc/bcpg/LiteralDataPacket.cs | 9 +- src/core/srcbc/cms/SignerInformation.cs | 1309 ++++----- src/core/srcbc/crypto/CryptoException.cs | 8 +- .../srcbc/crypto/agreement/DHAgreement.cs | 14 +- .../srcbc/crypto/agreement/srp/SRP6Client.cs | 93 + .../srcbc/crypto/agreement/srp/SRP6Server.cs | 90 + .../crypto/agreement/srp/SRP6Utilities.cs | 85 + .../agreement/srp/SRP6VerifierGenerator.cs | 49 + .../srcbc/crypto/digests/GeneralDigest.cs | 2 +- src/core/srcbc/crypto/digests/Sha256Digest.cs | 258 +- src/core/srcbc/crypto/engines/DesEngine.cs | 12 +- src/core/srcbc/crypto/engines/TEAEngine.cs | 2 +- src/core/srcbc/crypto/engines/XTEAEngine.cs | 2 +- .../generators/DHBasicKeyPairGenerator.cs | 20 +- .../crypto/generators/DHKeyGeneratorHelper.cs | 73 +- .../crypto/generators/DHKeyPairGenerator.cs | 18 +- .../crypto/generators/DHParametersHelper.cs | 17 +- .../crypto/generators/DesEdeKeyGenerator.cs | 3 +- .../crypto/generators/DesKeyGenerator.cs | 23 + .../crypto/generators/ECKeyPairGenerator.cs | 10 + .../generators/ElGamalKeyPairGenerator.cs | 12 +- src/core/srcbc/crypto/io/DigestStream.cs | 239 +- src/core/srcbc/crypto/io/MacStream.cs | 238 +- src/core/srcbc/crypto/io/SignerStream.cs | 137 + .../parameters/DHKeyGenerationParameters.cs | 8 +- .../srcbc/crypto/parameters/DHParameters.cs | 36 +- .../ElGamalKeyGenerationParameters.cs | 8 +- .../srcbc/crypto/signers/DsaDigestSigner.cs | 25 +- .../srcbc/crypto/signers/GenericSigner.cs | 129 + .../srcbc/crypto/tls/TlsCipherSuiteManager.cs | 38 +- src/core/srcbc/crypto/tls/TlsDssSigner.cs | 16 + .../srcbc/crypto/tls/TlsProtocolHandler.cs | 2371 ++++++++-------- src/core/srcbc/crypto/tls/TlsRsaSigner.cs | 17 + src/core/srcbc/math/BigInteger.cs | 4 +- src/core/srcbc/openpgp/PgpLiteralData.cs | 6 + src/core/srcbc/openssl/EncryptionException.cs | 22 + src/core/srcbc/openssl/PEMReader.cs | 4 +- src/core/srcbc/openssl/PEMUtilities.cs | 6 +- src/core/srcbc/openssl/PasswordException.cs | 22 + src/core/srcbc/pkix/CertStatus.cs | 35 + src/core/srcbc/pkix/PkixAttrCertChecker.cs | 57 + .../srcbc/pkix/PkixAttrCertPathBuilder.cs | 215 ++ .../srcbc/pkix/PkixAttrCertPathValidator.cs | 76 + src/core/srcbc/pkix/PkixBuilderParameters.cs | 140 + src/core/srcbc/pkix/PkixCertPath.cs | 474 ++++ src/core/srcbc/pkix/PkixCertPathBuilder.cs | 205 ++ .../pkix/PkixCertPathBuilderException.cs | 19 + .../srcbc/pkix/PkixCertPathBuilderResult.cs | 45 + src/core/srcbc/pkix/PkixCertPathChecker.cs | 101 + src/core/srcbc/pkix/PkixCertPathValidator.cs | 419 +++ .../pkix/PkixCertPathValidatorException.cs | 218 ++ .../srcbc/pkix/PkixCertPathValidatorResult.cs | 69 + .../pkix/PkixCertPathValidatorUtilities.cs | 1274 +++++++++ .../srcbc/pkix/PkixNameConstraintValidator.cs | 1935 +++++++++++++ .../PkixNameConstraintValidatorException.cs | 12 + src/core/srcbc/pkix/PkixParameters.cs | 948 +++++++ src/core/srcbc/pkix/PkixPolicyNode.cs | 156 ++ src/core/srcbc/pkix/ReasonsMask.cs | 96 + .../srcbc/pkix/Rfc3280CertPathUtilities.cs | 2478 +++++++++++++++++ .../srcbc/pkix/Rfc3281CertPathUtilities.cs | 608 ++++ src/core/srcbc/pkix/TrustAnchor.cs | 259 ++ src/core/srcbc/security/PrivateKeyFactory.cs | 422 +-- src/core/srcbc/security/PublicKeyFactory.cs | 8 +- src/core/srcbc/util/Arrays.cs | 22 + src/core/srcbc/util/BigIntegers.cs | 43 + src/core/srcbc/x509/X509CertificateParser.cs | 2 +- .../srcbc/x509/X509V3CertificateGenerator.cs | 43 + 85 files changed, 16254 insertions(+), 4487 deletions(-) create mode 100644 src/core/srcbc/asn1/BerApplicationSpecific.cs create mode 100644 src/core/srcbc/asn1/BerApplicationSpecificParser.cs create mode 100644 src/core/srcbc/asn1/IAsn1ApplicationSpecificParser.cs create mode 100644 src/core/srcbc/asn1/cms/AuthenticatedData.cs create mode 100644 src/core/srcbc/asn1/cms/AuthenticatedDataParser.cs create mode 100644 src/core/srcbc/asn1/eac/EACObjectIdentifiers.cs create mode 100644 src/core/srcbc/crypto/agreement/srp/SRP6Client.cs create mode 100644 src/core/srcbc/crypto/agreement/srp/SRP6Server.cs create mode 100644 src/core/srcbc/crypto/agreement/srp/SRP6Utilities.cs create mode 100644 src/core/srcbc/crypto/agreement/srp/SRP6VerifierGenerator.cs create mode 100644 src/core/srcbc/crypto/io/SignerStream.cs create mode 100644 src/core/srcbc/crypto/signers/GenericSigner.cs create mode 100644 src/core/srcbc/crypto/tls/TlsDssSigner.cs create mode 100644 src/core/srcbc/crypto/tls/TlsRsaSigner.cs create mode 100644 src/core/srcbc/openssl/EncryptionException.cs create mode 100644 src/core/srcbc/openssl/PasswordException.cs create mode 100644 src/core/srcbc/pkix/CertStatus.cs create mode 100644 src/core/srcbc/pkix/PkixAttrCertChecker.cs create mode 100644 src/core/srcbc/pkix/PkixAttrCertPathBuilder.cs create mode 100644 src/core/srcbc/pkix/PkixAttrCertPathValidator.cs create mode 100644 src/core/srcbc/pkix/PkixBuilderParameters.cs create mode 100644 src/core/srcbc/pkix/PkixCertPath.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathBuilder.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathBuilderException.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathBuilderResult.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathChecker.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathValidator.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathValidatorException.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathValidatorResult.cs create mode 100644 src/core/srcbc/pkix/PkixCertPathValidatorUtilities.cs create mode 100644 src/core/srcbc/pkix/PkixNameConstraintValidator.cs create mode 100644 src/core/srcbc/pkix/PkixNameConstraintValidatorException.cs create mode 100644 src/core/srcbc/pkix/PkixParameters.cs create mode 100644 src/core/srcbc/pkix/PkixPolicyNode.cs create mode 100644 src/core/srcbc/pkix/ReasonsMask.cs create mode 100644 src/core/srcbc/pkix/Rfc3280CertPathUtilities.cs create mode 100644 src/core/srcbc/pkix/Rfc3281CertPathUtilities.cs create mode 100644 src/core/srcbc/pkix/TrustAnchor.cs diff --git a/src/core/iTextSharp/text/pdf/codec/PngImage.cs b/src/core/iTextSharp/text/pdf/codec/PngImage.cs index bb39c20..200176c 100644 --- a/src/core/iTextSharp/text/pdf/codec/PngImage.cs +++ b/src/core/iTextSharp/text/pdf/codec/PngImage.cs @@ -1,11 +1,11 @@ -using System; -using System.IO; -using System.Net; -using System.Text; -using System.util.zlib; -using System.util; -using iTextSharp.text; -using iTextSharp.text.pdf; +using System; +using System.IO; +using System.Net; +using System.Text; +using System.util.zlib; +using System.util; +using iTextSharp.text; +using iTextSharp.text.pdf; /* * Copyright 2003-2008 by Paulo Soares. * @@ -94,881 +94,882 @@ using iTextSharp.text.pdf; * use in the design, construction, operation or maintenance of any * nuclear facility. */ - -namespace iTextSharp.text.pdf.codec { - /** Reads a PNG image. All types of PNG can be read. - *

- * It is based in part in the JAI codec. - * - * @author Paulo Soares (psoares@consiste.pt) - */ - public class PngImage { - /** Some PNG specific values. */ - public static int[] PNGID = {137, 80, 78, 71, 13, 10, 26, 10}; - - /** A PNG marker. */ - public const String IHDR = "IHDR"; - - /** A PNG marker. */ - public const String PLTE = "PLTE"; - - /** A PNG marker. */ - public const String IDAT = "IDAT"; - - /** A PNG marker. */ - public const String IEND = "IEND"; - - /** A PNG marker. */ - public const String tRNS = "tRNS"; - - /** A PNG marker. */ - public const String pHYs = "pHYs"; - - /** A PNG marker. */ - public const String gAMA = "gAMA"; - - /** A PNG marker. */ - public const String cHRM = "cHRM"; - - /** A PNG marker. */ - public const String sRGB = "sRGB"; - - /** A PNG marker. */ - public const String iCCP = "iCCP"; - - private const int TRANSFERSIZE = 4096; - private const int PNG_FILTER_NONE = 0; - private const int PNG_FILTER_SUB = 1; - private const int PNG_FILTER_UP = 2; - private const int PNG_FILTER_AVERAGE = 3; - private const int PNG_FILTER_PAETH = 4; - private static PdfName[] intents = {PdfName.PERCEPTUAL, - PdfName.RELATIVECALORIMETRIC,PdfName.SATURATION,PdfName.ABSOLUTECALORIMETRIC}; - - Stream isp; - Stream dataStream; - int width; - int height; - int bitDepth; - int colorType; - int compressionMethod; - int filterMethod; - int interlaceMethod; - PdfDictionary additional = new PdfDictionary(); - byte[] image; - byte[] smask; - byte[] trans; - MemoryStream idat = new MemoryStream(); - int dpiX; - int dpiY; - float XYRatio; - bool genBWMask; - bool palShades; - int transRedGray = -1; - int transGreen = -1; - int transBlue = -1; - int inputBands; - int bytesPerPixel; // number of bytes per input pixel - byte[] colorTable; - float gamma = 1f; - bool hasCHRM = false; - float xW, yW, xR, yR, xG, yG, xB, yB; - PdfName intent; - ICC_Profile icc_profile; - - - - /** Creates a new instance of PngImage */ - PngImage(Stream isp) { - this.isp = isp; - } - - /** Reads a PNG from an url. - * @param url the url - * @throws IOException on error - * @return the image - */ - public static Image GetImage(Uri url) { - Stream isp = null; - try { - isp = WebRequest.Create(url).GetResponse().GetResponseStream(); - Image img = GetImage(isp); - img.Url = url; - return img; - } - finally { - if (isp != null) { - isp.Close(); - } - } - } - - /** Reads a PNG from a stream. - * @param is the stream - * @throws IOException on error - * @return the image - */ - public static Image GetImage(Stream isp) { - PngImage png = new PngImage(isp); - return png.GetImage(); - } - - /** Reads a PNG from a file. - * @param file the file - * @throws IOException on error - * @return the image - */ - public static Image GetImage(String file) { - return GetImage(Utilities.ToURL(file)); - } - - /** Reads a PNG from a byte array. - * @param data the byte array - * @throws IOException on error - * @return the image - */ - public static Image GetImage(byte[] data) { - Stream isp = new MemoryStream(data); - Image img = GetImage(isp); - img.OriginalData = data; - return img; - } - - static bool CheckMarker(String s) { - if (s.Length != 4) - return false; - for (int k = 0; k < 4; ++k) { - char c = s[k]; - if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) - return false; - } - return true; - } - - void ReadPng() { - for (int i = 0; i < PNGID.Length; i++) { - if (PNGID[i] != isp.ReadByte()) { - throw new IOException("File is not a valid PNG."); - } - } - byte[] buffer = new byte[TRANSFERSIZE]; - while (true) { - int len = GetInt(isp); - String marker = GetString(isp); - if (len < 0 || !CheckMarker(marker)) - throw new IOException("Corrupted PNG file."); - if (IDAT.Equals(marker)) { - int size; - while (len != 0) { - size = isp.Read(buffer, 0, Math.Min(len, TRANSFERSIZE)); - if (size <= 0) - return; - idat.Write(buffer, 0, size); - len -= size; - } - } - else if (tRNS.Equals(marker)) { - switch (colorType) { - case 0: - if (len >= 2) { - len -= 2; - int gray = GetWord(isp); - if (bitDepth == 16) - transRedGray = gray; - else - additional.Put(PdfName.MASK, new PdfLiteral("["+gray+" "+gray+"]")); - } - break; - case 2: - if (len >= 6) { - len -= 6; - int red = GetWord(isp); - int green = GetWord(isp); - int blue = GetWord(isp); - if (bitDepth == 16) { - transRedGray = red; - transGreen = green; - transBlue = blue; - } - else - additional.Put(PdfName.MASK, new PdfLiteral("["+red+" "+red+" "+green+" "+green+" "+blue+" "+blue+"]")); - } - break; - case 3: - if (len > 0) { - trans = new byte[len]; - for (int k = 0; k < len; ++k) - trans[k] = (byte)isp.ReadByte(); - len = 0; - } - break; - } - Utilities.Skip(isp, len); - } - else if (IHDR.Equals(marker)) { - width = GetInt(isp); - height = GetInt(isp); - - bitDepth = isp.ReadByte(); - colorType = isp.ReadByte(); - compressionMethod = isp.ReadByte(); - filterMethod = isp.ReadByte(); - interlaceMethod = isp.ReadByte(); - } - else if (PLTE.Equals(marker)) { - if (colorType == 3) { - PdfArray colorspace = new PdfArray(); - colorspace.Add(PdfName.INDEXED); - colorspace.Add(GetColorspace()); - colorspace.Add(new PdfNumber(len / 3 - 1)); - ByteBuffer colortable = new ByteBuffer(); - while ((len--) > 0) { - colortable.Append_i(isp.ReadByte()); - } - colorspace.Add(new PdfString(colorTable = colortable.ToByteArray())); - additional.Put(PdfName.COLORSPACE, colorspace); - } - else { - Utilities.Skip(isp, len); - } - } - else if (pHYs.Equals(marker)) { - int dx = GetInt(isp); - int dy = GetInt(isp); - int unit = isp.ReadByte(); - if (unit == 1) { - dpiX = (int)((float)dx * 0.0254f + 0.5f); - dpiY = (int)((float)dy * 0.0254f + 0.5f); - } - else { - if (dy != 0) - XYRatio = (float)dx / (float)dy; - } - } - else if (cHRM.Equals(marker)) { - xW = (float)GetInt(isp) / 100000f; - yW = (float)GetInt(isp) / 100000f; - xR = (float)GetInt(isp) / 100000f; - yR = (float)GetInt(isp) / 100000f; - xG = (float)GetInt(isp) / 100000f; - yG = (float)GetInt(isp) / 100000f; - xB = (float)GetInt(isp) / 100000f; - yB = (float)GetInt(isp) / 100000f; - hasCHRM = !(Math.Abs(xW)<0.0001f||Math.Abs(yW)<0.0001f||Math.Abs(xR)<0.0001f||Math.Abs(yR)<0.0001f||Math.Abs(xG)<0.0001f||Math.Abs(yG)<0.0001f||Math.Abs(xB)<0.0001f||Math.Abs(yB)<0.0001f); - } - else if (sRGB.Equals(marker)) { - int ri = isp.ReadByte(); - intent = intents[ri]; - gamma = 2.2f; - xW = 0.3127f; - yW = 0.329f; - xR = 0.64f; - yR = 0.33f; - xG = 0.3f; - yG = 0.6f; - xB = 0.15f; - yB = 0.06f; - hasCHRM = true; - } - else if (gAMA.Equals(marker)) { - int gm = GetInt(isp); - if (gm != 0) { - gamma = 100000f / (float)gm; - if (!hasCHRM) { - xW = 0.3127f; - yW = 0.329f; - xR = 0.64f; - yR = 0.33f; - xG = 0.3f; - yG = 0.6f; - xB = 0.15f; - yB = 0.06f; - hasCHRM = true; - } - } - } - else if (iCCP.Equals(marker)) { - do { - --len; - } while (isp.ReadByte() != 0); - isp.ReadByte(); - --len; - byte[] icccom = new byte[len]; - int p = 0; - while (len > 0) { - int r = isp.Read(icccom, p, len); - if (r < 0) - throw new IOException("Premature end of file."); - p += r; - len -= r; - } - byte[] iccp = PdfReader.FlateDecode(icccom, true); - icccom = null; - try { - icc_profile = ICC_Profile.GetInstance(iccp); - } - catch { - icc_profile = null; - } - } - else if (IEND.Equals(marker)) { - break; - } - else { - Utilities.Skip(isp, len); - } - Utilities.Skip(isp, 4); - } - } - - PdfObject GetColorspace() { - if (icc_profile != null) { - if ((colorType & 2) == 0) - return PdfName.DEVICEGRAY; - else - return PdfName.DEVICERGB; - } - if (gamma == 1f && !hasCHRM) { - if ((colorType & 2) == 0) - return PdfName.DEVICEGRAY; - else - return PdfName.DEVICERGB; - } - else { - PdfArray array = new PdfArray(); - PdfDictionary dic = new PdfDictionary(); - if ((colorType & 2) == 0) { - if (gamma == 1f) - return PdfName.DEVICEGRAY; - array.Add(PdfName.CALGRAY); - dic.Put(PdfName.GAMMA, new PdfNumber(gamma)); - dic.Put(PdfName.WHITEPOINT, new PdfLiteral("[1 1 1]")); - array.Add(dic); - } - else { - PdfObject wp = new PdfLiteral("[1 1 1]"); - array.Add(PdfName.CALRGB); - if (gamma != 1f) { - PdfArray gm = new PdfArray(); - PdfNumber n = new PdfNumber(gamma); - gm.Add(n); - gm.Add(n); - gm.Add(n); - dic.Put(PdfName.GAMMA, gm); - } - if (hasCHRM) { - float z = yW*((xG-xB)*yR-(xR-xB)*yG+(xR-xG)*yB); - float YA = yR*((xG-xB)*yW-(xW-xB)*yG+(xW-xG)*yB)/z; - float XA = YA*xR/yR; - float ZA = YA*((1-xR)/yR-1); - float YB = -yG*((xR-xB)*yW-(xW-xB)*yR+(xW-xR)*yB)/z; - float XB = YB*xG/yG; - float ZB = YB*((1-xG)/yG-1); - float YC = yB*((xR-xG)*yW-(xW-xG)*yW+(xW-xR)*yG)/z; - float XC = YC*xB/yB; - float ZC = YC*((1-xB)/yB-1); - float XW = XA+XB+XC; - float YW = 1;//YA+YB+YC; - float ZW = ZA+ZB+ZC; - PdfArray wpa = new PdfArray(); - wpa.Add(new PdfNumber(XW)); - wpa.Add(new PdfNumber(YW)); - wpa.Add(new PdfNumber(ZW)); - wp = wpa; - PdfArray matrix = new PdfArray(); - matrix.Add(new PdfNumber(XA)); - matrix.Add(new PdfNumber(YA)); - matrix.Add(new PdfNumber(ZA)); - matrix.Add(new PdfNumber(XB)); - matrix.Add(new PdfNumber(YB)); - matrix.Add(new PdfNumber(ZB)); - matrix.Add(new PdfNumber(XC)); - matrix.Add(new PdfNumber(YC)); - matrix.Add(new PdfNumber(ZC)); - dic.Put(PdfName.MATRIX, matrix); - } - dic.Put(PdfName.WHITEPOINT, wp); - array.Add(dic); - } - return array; - } - } - - Image GetImage() { - ReadPng(); - int pal0 = 0; - int palIdx = 0; - palShades = false; - if (trans != null) { - for (int k = 0; k < trans.Length; ++k) { - int n = trans[k] & 0xff; - if (n == 0) { - ++pal0; - palIdx = k; - } - if (n != 0 && n != 255) { - palShades = true; - break; - } - } - } - if ((colorType & 4) != 0) - palShades = true; - genBWMask = (!palShades && (pal0 > 1 || transRedGray >= 0)); - if (!palShades && !genBWMask && pal0 == 1) { - additional.Put(PdfName.MASK, new PdfLiteral("["+palIdx+" "+palIdx+"]")); - } - bool needDecode = (interlaceMethod == 1) || (bitDepth == 16) || ((colorType & 4) != 0) || palShades || genBWMask; - switch (colorType) { - case 0: - inputBands = 1; - break; - case 2: - inputBands = 3; - break; - case 3: - inputBands = 1; - break; - case 4: - inputBands = 2; - break; - case 6: - inputBands = 4; - break; - } - if (needDecode) - DecodeIdat(); - int components = inputBands; - if ((colorType & 4) != 0) - --components; - int bpc = bitDepth; - if (bpc == 16) - bpc = 8; - Image img; + +namespace iTextSharp.text.pdf.codec { + /** Reads a PNG image. All types of PNG can be read. + *

+ * It is based in part in the JAI codec. + * + * @author Paulo Soares (psoares@consiste.pt) + */ + public class PngImage { + /** Some PNG specific values. */ + public static int[] PNGID = {137, 80, 78, 71, 13, 10, 26, 10}; + + /** A PNG marker. */ + public const String IHDR = "IHDR"; + + /** A PNG marker. */ + public const String PLTE = "PLTE"; + + /** A PNG marker. */ + public const String IDAT = "IDAT"; + + /** A PNG marker. */ + public const String IEND = "IEND"; + + /** A PNG marker. */ + public const String tRNS = "tRNS"; + + /** A PNG marker. */ + public const String pHYs = "pHYs"; + + /** A PNG marker. */ + public const String gAMA = "gAMA"; + + /** A PNG marker. */ + public const String cHRM = "cHRM"; + + /** A PNG marker. */ + public const String sRGB = "sRGB"; + + /** A PNG marker. */ + public const String iCCP = "iCCP"; + + private const int TRANSFERSIZE = 4096; + private const int PNG_FILTER_NONE = 0; + private const int PNG_FILTER_SUB = 1; + private const int PNG_FILTER_UP = 2; + private const int PNG_FILTER_AVERAGE = 3; + private const int PNG_FILTER_PAETH = 4; + private static PdfName[] intents = {PdfName.PERCEPTUAL, + PdfName.RELATIVECALORIMETRIC,PdfName.SATURATION,PdfName.ABSOLUTECALORIMETRIC}; + + Stream isp; + Stream dataStream; + int width; + int height; + int bitDepth; + int colorType; + int compressionMethod; + int filterMethod; + int interlaceMethod; + PdfDictionary additional = new PdfDictionary(); + byte[] image; + byte[] smask; + byte[] trans; + MemoryStream idat = new MemoryStream(); + int dpiX; + int dpiY; + float XYRatio; + bool genBWMask; + bool palShades; + int transRedGray = -1; + int transGreen = -1; + int transBlue = -1; + int inputBands; + int bytesPerPixel; // number of bytes per input pixel + byte[] colorTable; + float gamma = 1f; + bool hasCHRM = false; + float xW, yW, xR, yR, xG, yG, xB, yB; + PdfName intent; + ICC_Profile icc_profile; + + + + /** Creates a new instance of PngImage */ + PngImage(Stream isp) { + this.isp = isp; + } + + /** Reads a PNG from an url. + * @param url the url + * @throws IOException on error + * @return the image + */ + public static Image GetImage(Uri url) { + Stream isp = null; + try { + isp = WebRequest.Create(url).GetResponse().GetResponseStream(); + Image img = GetImage(isp); + img.Url = url; + return img; + } + finally { + if (isp != null) { + isp.Close(); + } + } + } + + /** Reads a PNG from a stream. + * @param is the stream + * @throws IOException on error + * @return the image + */ + public static Image GetImage(Stream isp) { + PngImage png = new PngImage(isp); + return png.GetImage(); + } + + /** Reads a PNG from a file. + * @param file the file + * @throws IOException on error + * @return the image + */ + public static Image GetImage(String file) { + return GetImage(Utilities.ToURL(file)); + } + + /** Reads a PNG from a byte array. + * @param data the byte array + * @throws IOException on error + * @return the image + */ + public static Image GetImage(byte[] data) { + Stream isp = new MemoryStream(data); + Image img = GetImage(isp); + img.OriginalData = data; + return img; + } + + static bool CheckMarker(String s) { + if (s.Length != 4) + return false; + for (int k = 0; k < 4; ++k) { + char c = s[k]; + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) + return false; + } + return true; + } + + void ReadPng() { + for (int i = 0; i < PNGID.Length; i++) { + if (PNGID[i] != isp.ReadByte()) { + throw new IOException("File is not a valid PNG."); + } + } + byte[] buffer = new byte[TRANSFERSIZE]; + while (true) { + int len = GetInt(isp); + String marker = GetString(isp); + if (len < 0 || !CheckMarker(marker)) + throw new IOException("Corrupted PNG file."); + if (IDAT.Equals(marker)) { + int size; + while (len != 0) { + size = isp.Read(buffer, 0, Math.Min(len, TRANSFERSIZE)); + if (size <= 0) + return; + idat.Write(buffer, 0, size); + len -= size; + } + } + else if (tRNS.Equals(marker)) { + switch (colorType) { + case 0: + if (len >= 2) { + len -= 2; + int gray = GetWord(isp); + if (bitDepth == 16) + transRedGray = gray; + else + additional.Put(PdfName.MASK, new PdfLiteral("["+gray+" "+gray+"]")); + } + break; + case 2: + if (len >= 6) { + len -= 6; + int red = GetWord(isp); + int green = GetWord(isp); + int blue = GetWord(isp); + if (bitDepth == 16) { + transRedGray = red; + transGreen = green; + transBlue = blue; + } + else + additional.Put(PdfName.MASK, new PdfLiteral("["+red+" "+red+" "+green+" "+green+" "+blue+" "+blue+"]")); + } + break; + case 3: + if (len > 0) { + trans = new byte[len]; + for (int k = 0; k < len; ++k) + trans[k] = (byte)isp.ReadByte(); + len = 0; + } + break; + } + Utilities.Skip(isp, len); + } + else if (IHDR.Equals(marker)) { + width = GetInt(isp); + height = GetInt(isp); + + bitDepth = isp.ReadByte(); + colorType = isp.ReadByte(); + compressionMethod = isp.ReadByte(); + filterMethod = isp.ReadByte(); + interlaceMethod = isp.ReadByte(); + } + else if (PLTE.Equals(marker)) { + if (colorType == 3) { + PdfArray colorspace = new PdfArray(); + colorspace.Add(PdfName.INDEXED); + colorspace.Add(GetColorspace()); + colorspace.Add(new PdfNumber(len / 3 - 1)); + ByteBuffer colortable = new ByteBuffer(); + while ((len--) > 0) { + colortable.Append_i(isp.ReadByte()); + } + colorspace.Add(new PdfString(colorTable = colortable.ToByteArray())); + additional.Put(PdfName.COLORSPACE, colorspace); + } + else { + Utilities.Skip(isp, len); + } + } + else if (pHYs.Equals(marker)) { + int dx = GetInt(isp); + int dy = GetInt(isp); + int unit = isp.ReadByte(); + if (unit == 1) { + dpiX = (int)((float)dx * 0.0254f + 0.5f); + dpiY = (int)((float)dy * 0.0254f + 0.5f); + } + else { + if (dy != 0) + XYRatio = (float)dx / (float)dy; + } + } + else if (cHRM.Equals(marker)) { + xW = (float)GetInt(isp) / 100000f; + yW = (float)GetInt(isp) / 100000f; + xR = (float)GetInt(isp) / 100000f; + yR = (float)GetInt(isp) / 100000f; + xG = (float)GetInt(isp) / 100000f; + yG = (float)GetInt(isp) / 100000f; + xB = (float)GetInt(isp) / 100000f; + yB = (float)GetInt(isp) / 100000f; + hasCHRM = !(Math.Abs(xW)<0.0001f||Math.Abs(yW)<0.0001f||Math.Abs(xR)<0.0001f||Math.Abs(yR)<0.0001f||Math.Abs(xG)<0.0001f||Math.Abs(yG)<0.0001f||Math.Abs(xB)<0.0001f||Math.Abs(yB)<0.0001f); + } + else if (sRGB.Equals(marker)) { + int ri = isp.ReadByte(); + intent = intents[ri]; + gamma = 2.2f; + xW = 0.3127f; + yW = 0.329f; + xR = 0.64f; + yR = 0.33f; + xG = 0.3f; + yG = 0.6f; + xB = 0.15f; + yB = 0.06f; + hasCHRM = true; + } + else if (gAMA.Equals(marker)) { + int gm = GetInt(isp); + if (gm != 0) { + gamma = 100000f / (float)gm; + if (!hasCHRM) { + xW = 0.3127f; + yW = 0.329f; + xR = 0.64f; + yR = 0.33f; + xG = 0.3f; + yG = 0.6f; + xB = 0.15f; + yB = 0.06f; + hasCHRM = true; + } + } + } + else if (iCCP.Equals(marker)) { + do { + --len; + } while (isp.ReadByte() != 0); + isp.ReadByte(); + --len; + byte[] icccom = new byte[len]; + int p = 0; + while (len > 0) { + int r = isp.Read(icccom, p, len); + if (r < 0) + throw new IOException("Premature end of file."); + p += r; + len -= r; + } + byte[] iccp = PdfReader.FlateDecode(icccom, true); + icccom = null; + try { + icc_profile = ICC_Profile.GetInstance(iccp); + } + catch { + icc_profile = null; + } + } + else if (IEND.Equals(marker)) { + break; + } + else { + Utilities.Skip(isp, len); + } + Utilities.Skip(isp, 4); + } + } + + PdfObject GetColorspace() { + if (icc_profile != null) { + if ((colorType & 2) == 0) + return PdfName.DEVICEGRAY; + else + return PdfName.DEVICERGB; + } + if (gamma == 1f && !hasCHRM) { + if ((colorType & 2) == 0) + return PdfName.DEVICEGRAY; + else + return PdfName.DEVICERGB; + } + else { + PdfArray array = new PdfArray(); + PdfDictionary dic = new PdfDictionary(); + if ((colorType & 2) == 0) { + if (gamma == 1f) + return PdfName.DEVICEGRAY; + array.Add(PdfName.CALGRAY); + dic.Put(PdfName.GAMMA, new PdfNumber(gamma)); + dic.Put(PdfName.WHITEPOINT, new PdfLiteral("[1 1 1]")); + array.Add(dic); + } + else { + PdfObject wp = new PdfLiteral("[1 1 1]"); + array.Add(PdfName.CALRGB); + if (gamma != 1f) { + PdfArray gm = new PdfArray(); + PdfNumber n = new PdfNumber(gamma); + gm.Add(n); + gm.Add(n); + gm.Add(n); + dic.Put(PdfName.GAMMA, gm); + } + if (hasCHRM) { + float z = yW*((xG-xB)*yR-(xR-xB)*yG+(xR-xG)*yB); + float YA = yR*((xG-xB)*yW-(xW-xB)*yG+(xW-xG)*yB)/z; + float XA = YA*xR/yR; + float ZA = YA*((1-xR)/yR-1); + float YB = -yG*((xR-xB)*yW-(xW-xB)*yR+(xW-xR)*yB)/z; + float XB = YB*xG/yG; + float ZB = YB*((1-xG)/yG-1); + float YC = yB*((xR-xG)*yW-(xW-xG)*yW+(xW-xR)*yG)/z; + float XC = YC*xB/yB; + float ZC = YC*((1-xB)/yB-1); + float XW = XA+XB+XC; + float YW = 1;//YA+YB+YC; + float ZW = ZA+ZB+ZC; + PdfArray wpa = new PdfArray(); + wpa.Add(new PdfNumber(XW)); + wpa.Add(new PdfNumber(YW)); + wpa.Add(new PdfNumber(ZW)); + wp = wpa; + PdfArray matrix = new PdfArray(); + matrix.Add(new PdfNumber(XA)); + matrix.Add(new PdfNumber(YA)); + matrix.Add(new PdfNumber(ZA)); + matrix.Add(new PdfNumber(XB)); + matrix.Add(new PdfNumber(YB)); + matrix.Add(new PdfNumber(ZB)); + matrix.Add(new PdfNumber(XC)); + matrix.Add(new PdfNumber(YC)); + matrix.Add(new PdfNumber(ZC)); + dic.Put(PdfName.MATRIX, matrix); + } + dic.Put(PdfName.WHITEPOINT, wp); + array.Add(dic); + } + return array; + } + } + + Image GetImage() { + ReadPng(); + int pal0 = 0; + int palIdx = 0; + palShades = false; + if (trans != null) { + for (int k = 0; k < trans.Length; ++k) { + int n = trans[k] & 0xff; + if (n == 0) { + ++pal0; + palIdx = k; + } + if (n != 0 && n != 255) { + palShades = true; + break; + } + } + } + if ((colorType & 4) != 0) + palShades = true; + genBWMask = (!palShades && (pal0 > 1 || transRedGray >= 0)); + if (!palShades && !genBWMask && pal0 == 1) { + additional.Put(PdfName.MASK, new PdfLiteral("["+palIdx+" "+palIdx+"]")); + } + bool needDecode = (interlaceMethod == 1) || (bitDepth == 16) || ((colorType & 4) != 0) || palShades || genBWMask; + switch (colorType) { + case 0: + inputBands = 1; + break; + case 2: + inputBands = 3; + break; + case 3: + inputBands = 1; + break; + case 4: + inputBands = 2; + break; + case 6: + inputBands = 4; + break; + } + if (needDecode) + DecodeIdat(); + int components = inputBands; + if ((colorType & 4) != 0) + --components; + int bpc = bitDepth; + if (bpc == 16) + bpc = 8; + Image img; if (image != null) { if (colorType == 3) img = new ImgRaw(width, height, components, bpc, image); else img = Image.GetInstance(width, height, components, bpc, image); - } - else { - img = new ImgRaw(width, height, components, bpc, idat.ToArray()); - img.Deflated = true; - PdfDictionary decodeparms = new PdfDictionary(); - decodeparms.Put(PdfName.BITSPERCOMPONENT, new PdfNumber(bitDepth)); - decodeparms.Put(PdfName.PREDICTOR, new PdfNumber(15)); - decodeparms.Put(PdfName.COLUMNS, new PdfNumber(width)); - decodeparms.Put(PdfName.COLORS, new PdfNumber((colorType == 3 || (colorType & 2) == 0) ? 1 : 3)); - additional.Put(PdfName.DECODEPARMS, decodeparms); - } - if (additional.Get(PdfName.COLORSPACE) == null) - additional.Put(PdfName.COLORSPACE, GetColorspace()); - if (intent != null) - additional.Put(PdfName.INTENT, intent); - if (additional.Size > 0) - img.Additional = additional; - if (icc_profile != null) - img.TagICC = icc_profile; - if (palShades) { - Image im2 = Image.GetInstance(width, height, 1, 8, smask); - im2.MakeMask(); - img.ImageMask = im2; - } - if (genBWMask) { - Image im2 = Image.GetInstance(width, height, 1, 1, smask); - im2.MakeMask(); - img.ImageMask = im2; - } - img.SetDpi(dpiX, dpiY); - img.XYRatio = XYRatio; - img.OriginalType = Image.ORIGINAL_PNG; - return img; - } - - void DecodeIdat() { - int nbitDepth = bitDepth; - if (nbitDepth == 16) - nbitDepth = 8; - int size = -1; - bytesPerPixel = (bitDepth == 16) ? 2 : 1; - switch (colorType) { - case 0: - size = (nbitDepth * width + 7) / 8 * height; - break; - case 2: - size = width * 3 * height; - bytesPerPixel *= 3; - break; - case 3: - if (interlaceMethod == 1) - size = (nbitDepth * width + 7) / 8 * height; - bytesPerPixel = 1; - break; - case 4: - size = width * height; - bytesPerPixel *= 2; - break; - case 6: - size = width * 3 * height; - bytesPerPixel *= 4; - break; - } - if (size >= 0) - image = new byte[size]; - if (palShades) - smask = new byte[width * height]; - else if (genBWMask) - smask = new byte[(width + 7) / 8 * height]; - idat.Position = 0; - dataStream = new ZInflaterInputStream(idat); - - if (interlaceMethod != 1) { - DecodePass(0, 0, 1, 1, width, height); - } - else { - DecodePass(0, 0, 8, 8, (width + 7)/8, (height + 7)/8); - DecodePass(4, 0, 8, 8, (width + 3)/8, (height + 7)/8); - DecodePass(0, 4, 4, 8, (width + 3)/4, (height + 3)/8); - DecodePass(2, 0, 4, 4, (width + 1)/4, (height + 3)/4); - DecodePass(0, 2, 2, 4, (width + 1)/2, (height + 1)/4); - DecodePass(1, 0, 2, 2, width/2, (height + 1)/2); - DecodePass(0, 1, 1, 2, width, height/2); - } - - } - - void DecodePass( int xOffset, int yOffset, - int xStep, int yStep, - int passWidth, int passHeight) { - if ((passWidth == 0) || (passHeight == 0)) { - return; - } - - int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; - byte[] curr = new byte[bytesPerRow]; - byte[] prior = new byte[bytesPerRow]; - - // Decode the (sub)image row-by-row - int srcY, dstY; - for (srcY = 0, dstY = yOffset; - srcY < passHeight; - srcY++, dstY += yStep) { - // Read the filter type byte and a row of data - int filter = 0; - try { - filter = dataStream.ReadByte(); - ReadFully(dataStream,curr, 0, bytesPerRow); - } catch { - // empty on purpose - } - - switch (filter) { - case PNG_FILTER_NONE: - break; - case PNG_FILTER_SUB: - DecodeSubFilter(curr, bytesPerRow, bytesPerPixel); - break; - case PNG_FILTER_UP: - DecodeUpFilter(curr, prior, bytesPerRow); - break; - case PNG_FILTER_AVERAGE: - DecodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel); - break; - case PNG_FILTER_PAETH: - DecodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel); - break; - default: - // Error -- uknown filter type - throw new Exception("PNG filter unknown."); - } - - ProcessPixels(curr, xOffset, xStep, dstY, passWidth); - - // Swap curr and prior - byte[] tmp = prior; - prior = curr; - curr = tmp; - } - } - - void ProcessPixels(byte[] curr, int xOffset, int step, int y, int width) { - int srcX, dstX; - - int[] outp = GetPixel(curr); - int sizes = 0; - switch (colorType) { - case 0: - case 3: - case 4: - sizes = 1; - break; - case 2: - case 6: - sizes = 3; - break; - } - if (image != null) { - dstX = xOffset; - int yStride = (sizes*this.width*(bitDepth == 16 ? 8 : bitDepth)+ 7)/8; - for (srcX = 0; srcX < width; srcX++) { - SetPixel(image, outp, inputBands * srcX, sizes, dstX, y, bitDepth, yStride); - dstX += step; - } - } - if (palShades) { - if ((colorType & 4) != 0) { - if (bitDepth == 16) { - for (int k = 0; k < width; ++k) { - int t = k * inputBands + sizes; - outp[t] = Util.USR(outp[t], 8); - } - } - int yStride = this.width; - dstX = xOffset; - for (srcX = 0; srcX < width; srcX++) { - SetPixel(smask, outp, inputBands * srcX + sizes, 1, dstX, y, 8, yStride); - dstX += step; - } - } - else { //colorType 3 - int yStride = this.width; - int[] v = new int[1]; - dstX = xOffset; - for (srcX = 0; srcX < width; srcX++) { - int idx = outp[srcX]; - if (idx < trans.Length) - v[0] = trans[idx]; - SetPixel(smask, v, 0, 1, dstX, y, 8, yStride); - dstX += step; - } - } - } - else if (genBWMask) { - switch (colorType) { - case 3: { - int yStride = (this.width + 7) / 8; - int[] v = new int[1]; - dstX = xOffset; - for (srcX = 0; srcX < width; srcX++) { - int idx = outp[srcX]; - v[0] = ((idx < trans.Length && trans[idx] == 0) ? 1 : 0); - SetPixel(smask, v, 0, 1, dstX, y, 1, yStride); - dstX += step; - } - break; - } - case 0: { - int yStride = (this.width + 7) / 8; - int[] v = new int[1]; - dstX = xOffset; - for (srcX = 0; srcX < width; srcX++) { - int g = outp[srcX]; - v[0] = (g == transRedGray ? 1 : 0); - SetPixel(smask, v, 0, 1, dstX, y, 1, yStride); - dstX += step; - } - break; - } - case 2: { - int yStride = (this.width + 7) / 8; - int[] v = new int[1]; - dstX = xOffset; - for (srcX = 0; srcX < width; srcX++) { - int markRed = inputBands * srcX; - v[0] = (outp[markRed] == transRedGray && outp[markRed + 1] == transGreen - && outp[markRed + 2] == transBlue ? 1 : 0); - SetPixel(smask, v, 0, 1, dstX, y, 1, yStride); - dstX += step; - } - break; - } - } - } - } - - static int GetPixel(byte[] image, int x, int y, int bitDepth, int bytesPerRow) { - if (bitDepth == 8) { - int pos = bytesPerRow * y + x; - return image[pos] & 0xff; - } - else { - int pos = bytesPerRow * y + x / (8 / bitDepth); - int v = image[pos] >> (8 - bitDepth * (x % (8 / bitDepth))- bitDepth); - return v & ((1 << bitDepth) - 1); - } - } - - static void SetPixel(byte[] image, int[] data, int offset, int size, int x, int y, int bitDepth, int bytesPerRow) { - if (bitDepth == 8) { - int pos = bytesPerRow * y + size * x; - for (int k = 0; k < size; ++k) - image[pos + k] = (byte)data[k + offset]; - } - else if (bitDepth == 16) { - int pos = bytesPerRow * y + size * x; - for (int k = 0; k < size; ++k) - image[pos + k] = (byte)(data[k + offset] >> 8); - } - else { - int pos = bytesPerRow * y + x / (8 / bitDepth); - int v = data[offset] << (8 - bitDepth * (x % (8 / bitDepth))- bitDepth); - image[pos] |= (byte)v; - } - } - - int[] GetPixel(byte[] curr) { - switch (bitDepth) { - case 8: { - int[] outp = new int[curr.Length]; - for (int k = 0; k < outp.Length; ++k) - outp[k] = curr[k] & 0xff; - return outp; - } - case 16: { - int[] outp = new int[curr.Length / 2]; - for (int k = 0; k < outp.Length; ++k) - outp[k] = ((curr[k * 2] & 0xff) << 8) + (curr[k * 2 + 1] & 0xff); - return outp; - } - default: { - int[] outp = new int[curr.Length * 8 / bitDepth]; - int idx = 0; - int passes = 8 / bitDepth; - int mask = (1 << bitDepth) - 1; - for (int k = 0; k < curr.Length; ++k) { - for (int j = passes - 1; j >= 0; --j) { - outp[idx++] = Util.USR(curr[k], bitDepth * j) & mask; - } - } - return outp; - } - } - } - - private static void DecodeSubFilter(byte[] curr, int count, int bpp) { - for (int i = bpp; i < count; i++) { - int val; - - val = curr[i] & 0xff; - val += curr[i - bpp] & 0xff; - - curr[i] = (byte)val; - } - } - - private static void DecodeUpFilter(byte[] curr, byte[] prev, - int count) { - for (int i = 0; i < count; i++) { - int raw = curr[i] & 0xff; - int prior = prev[i] & 0xff; - - curr[i] = (byte)(raw + prior); - } - } - - private static void DecodeAverageFilter(byte[] curr, byte[] prev, - int count, int bpp) { - int raw, priorPixel, priorRow; - - for (int i = 0; i < bpp; i++) { - raw = curr[i] & 0xff; - priorRow = prev[i] & 0xff; - - curr[i] = (byte)(raw + priorRow/2); - } - - for (int i = bpp; i < count; i++) { - raw = curr[i] & 0xff; - priorPixel = curr[i - bpp] & 0xff; - priorRow = prev[i] & 0xff; - - curr[i] = (byte)(raw + (priorPixel + priorRow)/2); - } - } - - private static int PaethPredictor(int a, int b, int c) { - int p = a + b - c; - int pa = Math.Abs(p - a); - int pb = Math.Abs(p - b); - int pc = Math.Abs(p - c); - - if ((pa <= pb) && (pa <= pc)) { - return a; - } else if (pb <= pc) { - return b; - } else { - return c; - } - } - - private static void DecodePaethFilter(byte[] curr, byte[] prev, - int count, int bpp) { - int raw, priorPixel, priorRow, priorRowPixel; - - for (int i = 0; i < bpp; i++) { - raw = curr[i] & 0xff; - priorRow = prev[i] & 0xff; - - curr[i] = (byte)(raw + priorRow); - } - - for (int i = bpp; i < count; i++) { - raw = curr[i] & 0xff; - priorPixel = curr[i - bpp] & 0xff; - priorRow = prev[i] & 0xff; - priorRowPixel = prev[i - bpp] & 0xff; - - curr[i] = (byte)(raw + PaethPredictor(priorPixel, - priorRow, - priorRowPixel)); - } - } - - /** - * Gets an int from an Stream. - * - * @param is an Stream - * @return the value of an int - */ - - public static int GetInt(Stream isp) { - return (isp.ReadByte() << 24) + (isp.ReadByte() << 16) + (isp.ReadByte() << 8) + isp.ReadByte(); - } - - /** - * Gets a word from an Stream. - * - * @param is an Stream - * @return the value of an int - */ - - public static int GetWord(Stream isp) { - return (isp.ReadByte() << 8) + isp.ReadByte(); - } - - /** - * Gets a String from an Stream. - * - * @param is an Stream - * @return the value of an int - */ - - public static String GetString(Stream isp) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < 4; i++) { - buf.Append((char)isp.ReadByte()); - } - return buf.ToString(); - } - - private static void ReadFully(Stream inp, byte[] b, int offset, int count) { - while (count > 0) { - int n = inp.Read(b, offset, count); - if (n <= 0) - throw new IOException("Insufficient data."); - count -= n; - offset += n; - } - } - } -} + } + else { + img = new ImgRaw(width, height, components, bpc, idat.ToArray()); + img.Deflated = true; + PdfDictionary decodeparms = new PdfDictionary(); + decodeparms.Put(PdfName.BITSPERCOMPONENT, new PdfNumber(bitDepth)); + decodeparms.Put(PdfName.PREDICTOR, new PdfNumber(15)); + decodeparms.Put(PdfName.COLUMNS, new PdfNumber(width)); + decodeparms.Put(PdfName.COLORS, new PdfNumber((colorType == 3 || (colorType & 2) == 0) ? 1 : 3)); + additional.Put(PdfName.DECODEPARMS, decodeparms); + } + if (additional.Get(PdfName.COLORSPACE) == null) + additional.Put(PdfName.COLORSPACE, GetColorspace()); + if (intent != null) + additional.Put(PdfName.INTENT, intent); + if (additional.Size > 0) + img.Additional = additional; + if (icc_profile != null) + img.TagICC = icc_profile; + if (palShades) { + Image im2 = Image.GetInstance(width, height, 1, 8, smask); + im2.MakeMask(); + img.ImageMask = im2; + } + if (genBWMask) { + Image im2 = Image.GetInstance(width, height, 1, 1, smask); + im2.MakeMask(); + img.ImageMask = im2; + } + img.SetDpi(dpiX, dpiY); + img.XYRatio = XYRatio; + img.OriginalType = Image.ORIGINAL_PNG; + return img; + } + + void DecodeIdat() { + int nbitDepth = bitDepth; + if (nbitDepth == 16) + nbitDepth = 8; + int size = -1; + bytesPerPixel = (bitDepth == 16) ? 2 : 1; + switch (colorType) { + case 0: + size = (nbitDepth * width + 7) / 8 * height; + break; + case 2: + size = width * 3 * height; + bytesPerPixel *= 3; + break; + case 3: + if (interlaceMethod == 1) + size = (nbitDepth * width + 7) / 8 * height; + bytesPerPixel = 1; + break; + case 4: + size = width * height; + bytesPerPixel *= 2; + break; + case 6: + size = width * 3 * height; + bytesPerPixel *= 4; + break; + } + if (size >= 0) + image = new byte[size]; + if (palShades) + smask = new byte[width * height]; + else if (genBWMask) + smask = new byte[(width + 7) / 8 * height]; + idat.Position = 0; + dataStream = new ZInflaterInputStream(idat); + + if (interlaceMethod != 1) { + DecodePass(0, 0, 1, 1, width, height); + } + else { + DecodePass(0, 0, 8, 8, (width + 7)/8, (height + 7)/8); + DecodePass(4, 0, 8, 8, (width + 3)/8, (height + 7)/8); + DecodePass(0, 4, 4, 8, (width + 3)/4, (height + 3)/8); + DecodePass(2, 0, 4, 4, (width + 1)/4, (height + 3)/4); + DecodePass(0, 2, 2, 4, (width + 1)/2, (height + 1)/4); + DecodePass(1, 0, 2, 2, width/2, (height + 1)/2); + DecodePass(0, 1, 1, 2, width, height/2); + } + + } + + void DecodePass( int xOffset, int yOffset, + int xStep, int yStep, + int passWidth, int passHeight) { + if ((passWidth == 0) || (passHeight == 0)) { + return; + } + + int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; + byte[] curr = new byte[bytesPerRow]; + byte[] prior = new byte[bytesPerRow]; + + // Decode the (sub)image row-by-row + int srcY, dstY; + for (srcY = 0, dstY = yOffset; + srcY < passHeight; + srcY++, dstY += yStep) { + // Read the filter type byte and a row of data + int filter = 0; + try { + filter = dataStream.ReadByte(); + ReadFully(dataStream,curr, 0, bytesPerRow); + } catch { + // empty on purpose + } + + switch (filter) { + case PNG_FILTER_NONE: + break; + case PNG_FILTER_SUB: + DecodeSubFilter(curr, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_UP: + DecodeUpFilter(curr, prior, bytesPerRow); + break; + case PNG_FILTER_AVERAGE: + DecodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_PAETH: + DecodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel); + break; + default: + // Error -- uknown filter type + throw new Exception("PNG filter unknown."); + } + + ProcessPixels(curr, xOffset, xStep, dstY, passWidth); + + // Swap curr and prior + byte[] tmp = prior; + prior = curr; + curr = tmp; + } + } + + void ProcessPixels(byte[] curr, int xOffset, int step, int y, int width) { + int srcX, dstX; + + int[] outp = GetPixel(curr); + int sizes = 0; + switch (colorType) { + case 0: + case 3: + case 4: + sizes = 1; + break; + case 2: + case 6: + sizes = 3; + break; + } + if (image != null) { + dstX = xOffset; + int yStride = (sizes*this.width*(bitDepth == 16 ? 8 : bitDepth)+ 7)/8; + for (srcX = 0; srcX < width; srcX++) { + SetPixel(image, outp, inputBands * srcX, sizes, dstX, y, bitDepth, yStride); + dstX += step; + } + } + if (palShades) { + if ((colorType & 4) != 0) { + if (bitDepth == 16) { + for (int k = 0; k < width; ++k) { + int t = k * inputBands + sizes; + outp[t] = Util.USR(outp[t], 8); + } + } + int yStride = this.width; + dstX = xOffset; + for (srcX = 0; srcX < width; srcX++) { + SetPixel(smask, outp, inputBands * srcX + sizes, 1, dstX, y, 8, yStride); + dstX += step; + } + } + else { //colorType 3 + int yStride = this.width; + int[] v = new int[1]; + dstX = xOffset; + for (srcX = 0; srcX < width; srcX++) { + int idx = outp[srcX]; + if (idx < trans.Length) + v[0] = trans[idx]; + SetPixel(smask, v, 0, 1, dstX, y, 8, yStride); + dstX += step; + } + } + } + else if (genBWMask) { + switch (colorType) { + case 3: { + int yStride = (this.width + 7) / 8; + int[] v = new int[1]; + dstX = xOffset; + for (srcX = 0; srcX < width; srcX++) { + int idx = outp[srcX]; + if (idx < trans.Length) + v[0] = (trans[idx] == 0 ? 1 : 0); + SetPixel(smask, v, 0, 1, dstX, y, 1, yStride); + dstX += step; + } + break; + } + case 0: { + int yStride = (this.width + 7) / 8; + int[] v = new int[1]; + dstX = xOffset; + for (srcX = 0; srcX < width; srcX++) { + int g = outp[srcX]; + v[0] = (g == transRedGray ? 1 : 0); + SetPixel(smask, v, 0, 1, dstX, y, 1, yStride); + dstX += step; + } + break; + } + case 2: { + int yStride = (this.width + 7) / 8; + int[] v = new int[1]; + dstX = xOffset; + for (srcX = 0; srcX < width; srcX++) { + int markRed = inputBands * srcX; + v[0] = (outp[markRed] == transRedGray && outp[markRed + 1] == transGreen + && outp[markRed + 2] == transBlue ? 1 : 0); + SetPixel(smask, v, 0, 1, dstX, y, 1, yStride); + dstX += step; + } + break; + } + } + } + } + + static int GetPixel(byte[] image, int x, int y, int bitDepth, int bytesPerRow) { + if (bitDepth == 8) { + int pos = bytesPerRow * y + x; + return image[pos] & 0xff; + } + else { + int pos = bytesPerRow * y + x / (8 / bitDepth); + int v = image[pos] >> (8 - bitDepth * (x % (8 / bitDepth))- bitDepth); + return v & ((1 << bitDepth) - 1); + } + } + + static void SetPixel(byte[] image, int[] data, int offset, int size, int x, int y, int bitDepth, int bytesPerRow) { + if (bitDepth == 8) { + int pos = bytesPerRow * y + size * x; + for (int k = 0; k < size; ++k) + image[pos + k] = (byte)data[k + offset]; + } + else if (bitDepth == 16) { + int pos = bytesPerRow * y + size * x; + for (int k = 0; k < size; ++k) + image[pos + k] = (byte)(data[k + offset] >> 8); + } + else { + int pos = bytesPerRow * y + x / (8 / bitDepth); + int v = data[offset] << (8 - bitDepth * (x % (8 / bitDepth))- bitDepth); + image[pos] |= (byte)v; + } + } + + int[] GetPixel(byte[] curr) { + switch (bitDepth) { + case 8: { + int[] outp = new int[curr.Length]; + for (int k = 0; k < outp.Length; ++k) + outp[k] = curr[k] & 0xff; + return outp; + } + case 16: { + int[] outp = new int[curr.Length / 2]; + for (int k = 0; k < outp.Length; ++k) + outp[k] = ((curr[k * 2] & 0xff) << 8) + (curr[k * 2 + 1] & 0xff); + return outp; + } + default: { + int[] outp = new int[curr.Length * 8 / bitDepth]; + int idx = 0; + int passes = 8 / bitDepth; + int mask = (1 << bitDepth) - 1; + for (int k = 0; k < curr.Length; ++k) { + for (int j = passes - 1; j >= 0; --j) { + outp[idx++] = Util.USR(curr[k], bitDepth * j) & mask; + } + } + return outp; + } + } + } + + private static void DecodeSubFilter(byte[] curr, int count, int bpp) { + for (int i = bpp; i < count; i++) { + int val; + + val = curr[i] & 0xff; + val += curr[i - bpp] & 0xff; + + curr[i] = (byte)val; + } + } + + private static void DecodeUpFilter(byte[] curr, byte[] prev, + int count) { + for (int i = 0; i < count; i++) { + int raw = curr[i] & 0xff; + int prior = prev[i] & 0xff; + + curr[i] = (byte)(raw + prior); + } + } + + private static void DecodeAverageFilter(byte[] curr, byte[] prev, + int count, int bpp) { + int raw, priorPixel, priorRow; + + for (int i = 0; i < bpp; i++) { + raw = curr[i] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + priorRow/2); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i] & 0xff; + priorPixel = curr[i - bpp] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + (priorPixel + priorRow)/2); + } + } + + private static int PaethPredictor(int a, int b, int c) { + int p = a + b - c; + int pa = Math.Abs(p - a); + int pb = Math.Abs(p - b); + int pc = Math.Abs(p - c); + + if ((pa <= pb) && (pa <= pc)) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + private static void DecodePaethFilter(byte[] curr, byte[] prev, + int count, int bpp) { + int raw, priorPixel, priorRow, priorRowPixel; + + for (int i = 0; i < bpp; i++) { + raw = curr[i] & 0xff; + priorRow = prev[i] & 0xff; + + curr[i] = (byte)(raw + priorRow); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i] & 0xff; + priorPixel = curr[i - bpp] & 0xff; + priorRow = prev[i] & 0xff; + priorRowPixel = prev[i - bpp] & 0xff; + + curr[i] = (byte)(raw + PaethPredictor(priorPixel, + priorRow, + priorRowPixel)); + } + } + + /** + * Gets an int from an Stream. + * + * @param is an Stream + * @return the value of an int + */ + + public static int GetInt(Stream isp) { + return (isp.ReadByte() << 24) + (isp.ReadByte() << 16) + (isp.ReadByte() << 8) + isp.ReadByte(); + } + + /** + * Gets a word from an Stream. + * + * @param is an Stream + * @return the value of an int + */ + + public static int GetWord(Stream isp) { + return (isp.ReadByte() << 8) + isp.ReadByte(); + } + + /** + * Gets a String from an Stream. + * + * @param is an Stream + * @return the value of an int + */ + + public static String GetString(Stream isp) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < 4; i++) { + buf.Append((char)isp.ReadByte()); + } + return buf.ToString(); + } + + private static void ReadFully(Stream inp, byte[] b, int offset, int count) { + while (count > 0) { + int n = inp.Read(b, offset, count); + if (n <= 0) + throw new IOException("Insufficient data."); + count -= n; + offset += n; + } + } + } +} diff --git a/src/core/itextsharp.csproj b/src/core/itextsharp.csproj index 450ed7a..f8f7499 100644 --- a/src/core/itextsharp.csproj +++ b/src/core/itextsharp.csproj @@ -1,7 +1,7 @@ @@ -2282,6 +2282,16 @@ SubType = "Code" BuildAction = "Compile" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + = 0x1f) - throw new IOException("unsupported tag number"); - byte[] data = obj.GetDerEncoded(); this.isConstructed = isExplicit; @@ -65,6 +62,29 @@ namespace Org.BouncyCastle.Asn1 } } + public DerApplicationSpecific( + int tagNo, + Asn1EncodableVector vec) + { + this.tag = tagNo; + this.isConstructed = true; + MemoryStream bOut = new MemoryStream(); + + for (int i = 0; i != vec.Count; i++) + { + try + { + byte[] bs = vec[i].GetEncoded(); + bOut.Write(bs, 0, bs.Length); + } + catch (IOException e) + { + throw new InvalidOperationException("malformed object", e); + } + } + this.octets = bOut.ToArray(); + } + private int GetLengthOfLength( byte[] data) { @@ -93,6 +113,12 @@ namespace Org.BouncyCastle.Asn1 get { return tag; } } + /** + * Return the enclosed object assuming explicit tagging. + * + * @return the resulting object + * @throws IOException if reconstruction fails. + */ public Asn1Object GetObject() { return FromByteArray(GetContents()); @@ -108,11 +134,16 @@ namespace Org.BouncyCastle.Asn1 public Asn1Object GetObject( int derTagNo) { - if (tag >= 0x1f) + if (derTagNo >= 0x1f) throw new IOException("unsupported tag number"); - byte[] tmp = this.GetEncoded(); - tmp[0] = (byte) derTagNo; + byte[] orig = this.GetEncoded(); + byte[] tmp = ReplaceTagNumber(derTagNo, orig); + + if ((orig[0] & Asn1Tags.Constructed) != 0) + { + tmp[0] |= Asn1Tags.Constructed; + } return FromByteArray(tmp);; } @@ -146,5 +177,46 @@ namespace Org.BouncyCastle.Asn1 { return isConstructed.GetHashCode() ^ tag.GetHashCode() ^ Arrays.GetHashCode(octets); } + + private byte[] ReplaceTagNumber( + int newTag, + byte[] input) + { + int tagNo = input[0] & 0x1f; + int index = 1; + // + // with tagged object tag number is bottom 5 bits, or stored at the start of the content + // + if (tagNo == 0x1f) + { + tagNo = 0; + + int b = input[index++] & 0xff; + + // X.690-0207 8.1.2.4.2 + // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." + if ((b & 0x7f) == 0) // Note: -1 will pass + { + throw new InvalidOperationException("corrupted stream - invalid high tag number found"); + } + + while ((b >= 0) && ((b & 0x80) != 0)) + { + tagNo |= (b & 0x7f); + tagNo <<= 7; + b = input[index++] & 0xff; + } + + tagNo |= (b & 0x7f); + } + + byte[] tmp = new byte[input.Length - index + 1]; + + Array.Copy(input, index, tmp, 1, tmp.Length - 1); + + tmp[0] = (byte)newTag; + + return tmp; + } } } diff --git a/src/core/srcbc/asn1/IAsn1ApplicationSpecificParser.cs b/src/core/srcbc/asn1/IAsn1ApplicationSpecificParser.cs new file mode 100644 index 0000000..efa5231 --- /dev/null +++ b/src/core/srcbc/asn1/IAsn1ApplicationSpecificParser.cs @@ -0,0 +1,10 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + public interface IAsn1ApplicationSpecificParser + : IAsn1Convertible + { + IAsn1Convertible ReadObject(); + } +} diff --git a/src/core/srcbc/asn1/cms/AuthenticatedData.cs b/src/core/srcbc/asn1/cms/AuthenticatedData.cs new file mode 100644 index 0000000..64e7a60 --- /dev/null +++ b/src/core/srcbc/asn1/cms/AuthenticatedData.cs @@ -0,0 +1,273 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cms +{ + public class AuthenticatedData + : Asn1Encodable + { + private DerInteger version; + private OriginatorInfo originatorInfo; + private Asn1Set recipientInfos; + private AlgorithmIdentifier macAlgorithm; + private AlgorithmIdentifier digestAlgorithm; + private ContentInfo encapsulatedContentInfo; + private Asn1Set authAttrs; + private Asn1OctetString mac; + private Asn1Set unauthAttrs; + + public AuthenticatedData( + OriginatorInfo originatorInfo, + Asn1Set recipientInfos, + AlgorithmIdentifier macAlgorithm, + AlgorithmIdentifier digestAlgorithm, + ContentInfo encapsulatedContent, + Asn1Set authAttrs, + Asn1OctetString mac, + Asn1Set unauthAttrs) + { + if (digestAlgorithm != null || authAttrs != null) + { + if (digestAlgorithm == null || authAttrs == null) + { + throw new ArgumentException("digestAlgorithm and authAttrs must be set together"); + } + } + + version = new DerInteger(calculateVersion(originatorInfo)); + + this.originatorInfo = originatorInfo; + this.macAlgorithm = macAlgorithm; + this.digestAlgorithm = digestAlgorithm; + this.recipientInfos = recipientInfos; + this.encapsulatedContentInfo = encapsulatedContent; + this.authAttrs = authAttrs; + this.mac = mac; + this.unauthAttrs = unauthAttrs; + } + + private AuthenticatedData( + Asn1Sequence seq) + { + int index = 0; + + version = (DerInteger)seq[index++]; + + Asn1Encodable tmp = seq[index++]; + + if (tmp is Asn1TaggedObject) + { + originatorInfo = OriginatorInfo.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++]; + } + + recipientInfos = Asn1Set.GetInstance(tmp); + macAlgorithm = AlgorithmIdentifier.GetInstance(seq[index++]); + + tmp = seq[index++]; + + if (tmp is Asn1TaggedObject) + { + digestAlgorithm = AlgorithmIdentifier.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++]; + } + + encapsulatedContentInfo = ContentInfo.GetInstance(tmp); + + tmp = seq[index++]; + + if (tmp is Asn1TaggedObject) + { + authAttrs = Asn1Set.GetInstance((Asn1TaggedObject)tmp, false); + tmp = seq[index++]; + } + + mac = Asn1OctetString.GetInstance(tmp); + + if (seq.Count > index) + { + unauthAttrs = Asn1Set.GetInstance((Asn1TaggedObject)seq[index], false); + } + } + + /** + * return an AuthenticatedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static AuthenticatedData GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + /** + * return an AuthenticatedData object from the given object. + * + * @param obj the object we want converted. + * @throws IllegalArgumentException if the object cannot be converted. + */ + public static AuthenticatedData GetInstance( + Object obj) + { + if (obj == null || obj is AuthenticatedData) + { + return (AuthenticatedData)obj; + } + + if (obj is Asn1Sequence) + { + return new AuthenticatedData((Asn1Sequence)obj); + } + + throw new ArgumentException("Invalid AuthenticatedData: " + obj.GetType().Name); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo GetOriginatorInfo() + { + return originatorInfo; + } + + public Asn1Set GetRecipientInfos() + { + return recipientInfos; + } + + public AlgorithmIdentifier GetMacAlgorithm() + { + return macAlgorithm; + } + + public ContentInfo GetEncapsulatedContentInfo() + { + return encapsulatedContentInfo; + } + + public Asn1Set GetAuthAttrs() + { + return authAttrs; + } + + public Asn1OctetString GetMac() + { + return mac; + } + + public Asn1Set GetUnauthAttrs() + { + return unauthAttrs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+	     * AuthenticatedData ::= SEQUENCE {
+	     *       version CMSVersion,
+	     *       originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+	     *       recipientInfos RecipientInfos,
+	     *       macAlgorithm MessageAuthenticationCodeAlgorithm,
+	     *       digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
+	     *       encapContentInfo EncapsulatedContentInfo,
+	     *       authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
+	     *       mac MessageAuthenticationCode,
+	     *       unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
+	     *
+	     * AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
+	     *
+	     * UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
+	     *
+	     * MessageAuthenticationCode ::= OCTET STRING
+	     * 
+ */ + public override Asn1Object ToAsn1Object() + { + Asn1EncodableVector v = new Asn1EncodableVector(version); + + if (originatorInfo != null) + { + v.Add(new DerTaggedObject(false, 0, originatorInfo)); + } + + v.Add(recipientInfos); + v.Add(macAlgorithm); + + if (digestAlgorithm != null) + { + v.Add(new DerTaggedObject(false, 1, digestAlgorithm)); + } + + v.Add(encapsulatedContentInfo); + + if (authAttrs != null) + { + v.Add(new DerTaggedObject(false, 2, authAttrs)); + } + + v.Add(mac); + + if (unauthAttrs != null) + { + v.Add(new DerTaggedObject(false, 3, unauthAttrs)); + } + + return new BerSequence(v); + } + + public static int calculateVersion(OriginatorInfo origInfo) + { + if (origInfo == null) + { + return 0; + } + else + { + int ver = 0; + + foreach (object obj in origInfo.Certificates) + { + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tag = (Asn1TaggedObject)obj; + + if (tag.TagNo == 2) + { + ver = 1; + } + else if (tag.TagNo == 3) + { + ver = 3; + break; + } + } + } + + foreach (object obj in origInfo.Crls) + { + if (obj is Asn1TaggedObject) + { + Asn1TaggedObject tag = (Asn1TaggedObject)obj; + + if (tag.TagNo == 1) + { + ver = 3; + break; + } + } + } + + return ver; + } + } + } +} diff --git a/src/core/srcbc/asn1/cms/AuthenticatedDataParser.cs b/src/core/srcbc/asn1/cms/AuthenticatedDataParser.cs new file mode 100644 index 0000000..392229a --- /dev/null +++ b/src/core/srcbc/asn1/cms/AuthenticatedDataParser.cs @@ -0,0 +1,164 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Asn1.Cms +{ + /** + * Produce an object suitable for an ASN1OutputStream. + *
+	 * AuthenticatedData ::= SEQUENCE {
+	 *       version CMSVersion,
+	 *       originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+	 *       recipientInfos RecipientInfos,
+	 *       macAlgorithm MessageAuthenticationCodeAlgorithm,
+	 *       digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
+	 *       encapContentInfo EncapsulatedContentInfo,
+	 *       authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
+	 *       mac MessageAuthenticationCode,
+	 *       unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
+	 *
+	 * AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
+	 *
+	 * UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
+	 *
+	 * MessageAuthenticationCode ::= OCTET STRING
+	 * 
+ */ + public class AuthenticatedDataParser + { + private Asn1SequenceParser seq; + private DerInteger version; + private IAsn1Convertible nextObject; + private bool originatorInfoCalled; + + public AuthenticatedDataParser( + Asn1SequenceParser seq) + { + this.seq = seq; + this.version = (DerInteger)seq.ReadObject(); + } + + public DerInteger Version + { + get { return version; } + } + + public OriginatorInfo GetOriginatorInfo() + { + originatorInfoCalled = true; + + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser && ((Asn1TaggedObjectParser)nextObject).TagNo == 0) + { + Asn1SequenceParser originatorInfo = (Asn1SequenceParser) ((Asn1TaggedObjectParser)nextObject).GetObjectParser(Asn1Tags.Sequence, false); + nextObject = null; + return OriginatorInfo.GetInstance(originatorInfo.ToAsn1Object()); + } + + return null; + } + + public Asn1SetParser GetRecipientInfos() + { + if (!originatorInfoCalled) + { + GetOriginatorInfo(); + } + + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + Asn1SetParser recipientInfos = (Asn1SetParser)nextObject; + nextObject = null; + return recipientInfos; + } + + public AlgorithmIdentifier GetMacAlgorithm() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + Asn1SequenceParser o = (Asn1SequenceParser)nextObject; + nextObject = null; + return AlgorithmIdentifier.GetInstance(o.ToAsn1Object()); + } + + return null; + } + + public ContentInfoParser GetEnapsulatedContentInfo() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + Asn1SequenceParser o = (Asn1SequenceParser)nextObject; + nextObject = null; + return new ContentInfoParser(o); + } + + return null; + } + + public Asn1SetParser GetAuthAttrs() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject is Asn1TaggedObjectParser) + { + IAsn1Convertible o = nextObject; + nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + return null; + } + + public Asn1OctetString GetMac() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + IAsn1Convertible o = nextObject; + nextObject = null; + + return Asn1OctetString.GetInstance(o.ToAsn1Object()); + } + + public Asn1SetParser GetUnauthAttrs() + { + if (nextObject == null) + { + nextObject = seq.ReadObject(); + } + + if (nextObject != null) + { + IAsn1Convertible o = nextObject; + nextObject = null; + return (Asn1SetParser)((Asn1TaggedObjectParser)o).GetObjectParser(Asn1Tags.Set, false); + } + + return null; + } + } +} diff --git a/src/core/srcbc/asn1/eac/EACObjectIdentifiers.cs b/src/core/srcbc/asn1/eac/EACObjectIdentifiers.cs new file mode 100644 index 0000000..d0e07bc --- /dev/null +++ b/src/core/srcbc/asn1/eac/EACObjectIdentifiers.cs @@ -0,0 +1,48 @@ +using System; + +using Org.BouncyCastle.Asn1; + +namespace Org.BouncyCastle.Asn1.Eac +{ + public abstract class EacObjectIdentifiers + { + // bsi-de OBJECT IDENTIFIER ::= { + // itu-t(0) identified-organization(4) etsi(0) + // reserved(127) etsi-identified-organization(0) 7 + // } + public static readonly DerObjectIdentifier bsi_de = new DerObjectIdentifier("0.4.0.127.0.7"); + + // id-PK OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 1 + // } + public static readonly DerObjectIdentifier id_PK = new DerObjectIdentifier(bsi_de + ".2.2.1"); + + public static readonly DerObjectIdentifier id_PK_DH = new DerObjectIdentifier(id_PK + ".1"); + public static readonly DerObjectIdentifier id_PK_ECDH = new DerObjectIdentifier(id_PK + ".2"); + + // id-CA OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 3 + // } + public static readonly DerObjectIdentifier id_CA = new DerObjectIdentifier(bsi_de + ".2.2.3"); + public static readonly DerObjectIdentifier id_CA_DH = new DerObjectIdentifier(id_CA + ".1"); + public static readonly DerObjectIdentifier id_CA_DH_3DES_CBC_CBC = new DerObjectIdentifier(id_CA_DH + ".1"); + public static readonly DerObjectIdentifier id_CA_ECDH = new DerObjectIdentifier(id_CA + ".2"); + public static readonly DerObjectIdentifier id_CA_ECDH_3DES_CBC_CBC = new DerObjectIdentifier(id_CA_ECDH + ".1"); + + // + // id-TA OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 2 + // } + public static readonly DerObjectIdentifier id_TA = new DerObjectIdentifier(bsi_de + ".2.2.2"); + + public static readonly DerObjectIdentifier id_TA_RSA = new DerObjectIdentifier(id_TA + ".1"); + public static readonly DerObjectIdentifier id_TA_RSA_v1_5_SHA_1 = new DerObjectIdentifier(id_TA_RSA + ".1"); + public static readonly DerObjectIdentifier id_TA_RSA_v1_5_SHA_256 = new DerObjectIdentifier(id_TA_RSA + ".2"); + public static readonly DerObjectIdentifier id_TA_RSA_PSS_SHA_1 = new DerObjectIdentifier(id_TA_RSA + ".3"); + public static readonly DerObjectIdentifier id_TA_RSA_PSS_SHA_256 = new DerObjectIdentifier(id_TA_RSA + ".4"); + public static readonly DerObjectIdentifier id_TA_ECDSA = new DerObjectIdentifier(id_TA + ".2"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_1 = new DerObjectIdentifier(id_TA_ECDSA + ".1"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_224 = new DerObjectIdentifier(id_TA_ECDSA + ".2"); + public static readonly DerObjectIdentifier id_TA_ECDSA_SHA_256 = new DerObjectIdentifier(id_TA_ECDSA + ".3"); + } +} diff --git a/src/core/srcbc/asn1/nist/NISTNamedCurves.cs b/src/core/srcbc/asn1/nist/NISTNamedCurves.cs index 463405f..56bce95 100644 --- a/src/core/srcbc/asn1/nist/NISTNamedCurves.cs +++ b/src/core/srcbc/asn1/nist/NISTNamedCurves.cs @@ -37,9 +37,10 @@ namespace Org.BouncyCastle.Asn1.Nist DefineCurve("B-233", SecObjectIdentifiers.SecT233r1); DefineCurve("B-163", SecObjectIdentifiers.SecT163r2); DefineCurve("P-521", SecObjectIdentifiers.SecP521r1); + DefineCurve("P-384", SecObjectIdentifiers.SecP384r1); DefineCurve("P-256", SecObjectIdentifiers.SecP256r1); DefineCurve("P-224", SecObjectIdentifiers.SecP224r1); - DefineCurve("P-384", SecObjectIdentifiers.SecP384r1); + DefineCurve("P-192", SecObjectIdentifiers.SecP192r1); } public static X9ECParameters GetByName( diff --git a/src/core/srcbc/asn1/pkcs/PKCSObjectIdentifiers.cs b/src/core/srcbc/asn1/pkcs/PKCSObjectIdentifiers.cs index cdeed37..4c430c2 100644 --- a/src/core/srcbc/asn1/pkcs/PKCSObjectIdentifiers.cs +++ b/src/core/srcbc/asn1/pkcs/PKCSObjectIdentifiers.cs @@ -151,6 +151,7 @@ namespace Org.BouncyCastle.Asn1.Pkcs // public const string IdCT = "1.2.840.113549.1.9.16.1"; + public static readonly DerObjectIdentifier IdCTAuthData = new DerObjectIdentifier(IdCT + ".2"); public static readonly DerObjectIdentifier IdCTTstInfo = new DerObjectIdentifier(IdCT + ".4"); public static readonly DerObjectIdentifier IdCTCompressedData = new DerObjectIdentifier(IdCT + ".9"); diff --git a/src/core/srcbc/asn1/util/Asn1Dump.cs b/src/core/srcbc/asn1/util/Asn1Dump.cs index f5d5ed7..9805a98 100644 --- a/src/core/srcbc/asn1/util/Asn1Dump.cs +++ b/src/core/srcbc/asn1/util/Asn1Dump.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.IO; using System.Text; using Org.BouncyCastle.Utilities; @@ -16,6 +17,7 @@ namespace Org.BouncyCastle.Asn1.Utilities } private const string Tab = " "; + private const int SampleSize = 32; /** * dump a Der object as a formatted string with indentation @@ -24,6 +26,7 @@ namespace Org.BouncyCastle.Asn1.Utilities */ private static string AsString( string indent, + bool verbose, Asn1Object obj) { if (obj is Asn1Sequence) @@ -57,7 +60,7 @@ namespace Org.BouncyCastle.Asn1.Utilities } else { - buf.Append(AsString(tab, o.ToAsn1Object())); + buf.Append(AsString(tab, verbose, o.ToAsn1Object())); } } return buf.ToString(); @@ -97,7 +100,7 @@ namespace Org.BouncyCastle.Asn1.Utilities } else { - buf.Append(AsString(tab, o.GetObject())); + buf.Append(AsString(tab, verbose, o.GetObject())); } return buf.ToString(); @@ -121,7 +124,7 @@ namespace Org.BouncyCastle.Asn1.Utilities } else { - buf.Append(AsString(tab, o.ToAsn1Object())); + buf.Append(AsString(tab, verbose, o.ToAsn1Object())); } } @@ -146,7 +149,7 @@ namespace Org.BouncyCastle.Asn1.Utilities } else { - buf.Append(AsString(tab, o.ToAsn1Object())); + buf.Append(AsString(tab, verbose, o.ToAsn1Object())); } } @@ -166,15 +169,22 @@ namespace Org.BouncyCastle.Asn1.Utilities } else if (obj is BerOctetString) { - return indent + "BER Octet String" + "[" + ((Asn1OctetString)obj).GetOctets().Length + "] " + NewLine; + byte[] octets = ((Asn1OctetString)obj).GetOctets(); + string extra = verbose ? dumpBinaryDataAsString(indent, octets) : ""; + return indent + "BER Octet String" + "[" + octets.Length + "] " + extra + NewLine; } else if (obj is DerOctetString) { - return indent + "DER Octet String" + "[" + ((Asn1OctetString)obj).GetOctets().Length + "] " + NewLine; + byte[] octets = ((Asn1OctetString)obj).GetOctets(); + string extra = verbose ? dumpBinaryDataAsString(indent, octets) : ""; + return indent + "DER Octet String" + "[" + octets.Length + "] " + extra + NewLine; } else if (obj is DerBitString) { - return indent + "DER Bit String" + "[" + ((DerBitString)obj).GetBytes().Length + ", " + ((DerBitString)obj).PadBits + "] " + NewLine; + DerBitString bt = (DerBitString)obj; + byte[] bytes = bt.GetBytes(); + string extra = verbose ? dumpBinaryDataAsString(indent, bytes) : ""; + return indent + "DER Bit String" + "[" + bytes.Length + ", " + bt.PadBits + "] " + extra + NewLine; } else if (obj is DerIA5String) { @@ -214,37 +224,138 @@ namespace Org.BouncyCastle.Asn1.Utilities return indent + "Unknown " + ((int)((DerUnknownTag)obj).Tag).ToString("X") + " " + Encoding.ASCII.GetString(hex, 0, hex.Length) + NewLine; } + else if (obj is BerApplicationSpecific) + { + return outputApplicationSpecific("BER", indent, verbose, (BerApplicationSpecific)obj); + } + else if (obj is DerApplicationSpecific) + { + return outputApplicationSpecific("DER", indent, verbose, (DerApplicationSpecific)obj); + } else { return indent + obj.ToString() + NewLine; } } + private static string outputApplicationSpecific( + string type, + string indent, + bool verbose, + DerApplicationSpecific app) + { + StringBuilder buf = new StringBuilder(); + + if (app.IsConstructed()) + { + try + { + Asn1Sequence s = Asn1Sequence.GetInstance(app.GetObject(Asn1Tags.Sequence)); + buf.Append(indent + type + " ApplicationSpecific[" + app.ApplicationTag + "]" + NewLine); + foreach (Asn1Encodable ae in s) + { + buf.Append(AsString(indent + Tab, verbose, ae.ToAsn1Object())); + } + } + catch (IOException e) + { + buf.Append(e); + } + return buf.ToString(); + } + + return indent + type + " ApplicationSpecific[" + app.ApplicationTag + "] (" + + Encoding.ASCII.GetString(Hex.Encode(app.GetContents())) + ")" + NewLine; + } + [Obsolete("Use version accepting Asn1Encodable")] public static string DumpAsString( object obj) { - if (obj is Asn1Object) + if (obj is Asn1Encodable) { - return AsString("", (Asn1Object)obj); - } - else if (obj is Asn1Encodable) - { - return AsString("", ((Asn1Encodable)obj).ToAsn1Object()); + return AsString("", false, ((Asn1Encodable)obj).ToAsn1Object()); } return "unknown object type " + obj.ToString(); } /** - * dump out a DER object as a formatted string + * dump out a DER object as a formatted string, in non-verbose mode * * @param obj the Asn1Encodable to be dumped out. + * @return the resulting string. */ public static string DumpAsString( Asn1Encodable obj) { - return AsString("", obj.ToAsn1Object()); + return DumpAsString(obj, false); + } + + /** + * Dump out the object as a string + * + * @param obj the Asn1Encodable to be dumped out. + * @param verbose if true, dump out the contents of octet and bit strings. + * @return the resulting string. + */ + public static string DumpAsString( + Asn1Encodable obj, + bool verbose) + { + return AsString("", verbose, obj.ToAsn1Object()); + } + + private static string dumpBinaryDataAsString(string indent, byte[] bytes) + { + indent += Tab; + + StringBuilder buf = new StringBuilder(NewLine); + + for (int i = 0; i < bytes.Length; i += SampleSize) + { + if (bytes.Length - i > SampleSize) + { + buf.Append(indent); + buf.Append(Encoding.ASCII.GetString(Hex.Encode(bytes, i, SampleSize))); + buf.Append(Tab); + buf.Append(calculateAscString(bytes, i, SampleSize)); + buf.Append(NewLine); + } + else + { + buf.Append(indent); + buf.Append(Encoding.ASCII.GetString(Hex.Encode(bytes, i, bytes.Length - i))); + for (int j = bytes.Length - i; j != SampleSize; j++) + { + buf.Append(" "); + } + buf.Append(Tab); + buf.Append(calculateAscString(bytes, i, bytes.Length - i)); + buf.Append(NewLine); + } + } + + return buf.ToString(); + } + + private static string calculateAscString( + byte[] bytes, + int off, + int len) + { + StringBuilder buf = new StringBuilder(); + + for (int i = off; i != off + len; i++) + { + char c = (char)bytes[i]; + if (c >= ' ' && c <= '~') + { + buf.Append(c); + } + } + + return buf.ToString(); } } } diff --git a/src/core/srcbc/asn1/x509/ExtendedKeyUsage.cs b/src/core/srcbc/asn1/x509/ExtendedKeyUsage.cs index e6116ae..0c1ab06 100644 --- a/src/core/srcbc/asn1/x509/ExtendedKeyUsage.cs +++ b/src/core/srcbc/asn1/x509/ExtendedKeyUsage.cs @@ -57,6 +57,17 @@ namespace Org.BouncyCastle.Asn1.X509 } } + public ExtendedKeyUsage( + params KeyPurposeID[] usages) + { + this.seq = new DerSequence(usages); + + foreach (KeyPurposeID usage in usages) + { + this.usageTable.Add(usage, usage); + } + } + public ExtendedKeyUsage( ArrayList usages) { diff --git a/src/core/srcbc/asn1/x509/V3TBSCertificateGenerator.cs b/src/core/srcbc/asn1/x509/V3TBSCertificateGenerator.cs index 9c77410..b09a087 100644 --- a/src/core/srcbc/asn1/x509/V3TBSCertificateGenerator.cs +++ b/src/core/srcbc/asn1/x509/V3TBSCertificateGenerator.cs @@ -32,6 +32,8 @@ namespace Org.BouncyCastle.Asn1.X509 internal X509Extensions extensions; private bool altNamePresentAndCritical; + private DerBitString issuerUniqueID; + private DerBitString subjectUniqueID; public V3TbsCertificateGenerator() { @@ -85,6 +87,18 @@ namespace Org.BouncyCastle.Asn1.X509 this.subject = subject; } + public void SetIssuerUniqueID( + DerBitString uniqueID) + { + this.issuerUniqueID = uniqueID; + } + + public void SetSubjectUniqueID( + DerBitString uniqueID) + { + this.subjectUniqueID = uniqueID; + } + public void SetSubjectPublicKeyInfo( SubjectPublicKeyInfo pubKeyInfo) { @@ -133,6 +147,16 @@ namespace Org.BouncyCastle.Asn1.X509 v.Add(subjectPublicKeyInfo); + if (issuerUniqueID != null) + { + v.Add(new DerTaggedObject(false, 1, issuerUniqueID)); + } + + if (subjectUniqueID != null) + { + v.Add(new DerTaggedObject(false, 2, subjectUniqueID)); + } + if (extensions != null) { v.Add(new DerTaggedObject(3, extensions)); diff --git a/src/core/srcbc/asn1/x509/X509Name.cs b/src/core/srcbc/asn1/x509/X509Name.cs index b8ac338..58649d7 100644 --- a/src/core/srcbc/asn1/x509/X509Name.cs +++ b/src/core/srcbc/asn1/x509/X509Name.cs @@ -1,1072 +1,1078 @@ -using System; -using System.Collections; -using System.Globalization; -using System.IO; -using System.Text; - -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Utilities.Encoders; - -namespace Org.BouncyCastle.Asn1.X509 -{ - /** - *
-    *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
-    *
-    *     RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
-    *
-    *     AttributeTypeAndValue ::= SEQUENCE {
-    *                                   type  OBJECT IDENTIFIER,
-    *                                   value ANY }
-    * 
- */ - public class X509Name - : Asn1Encodable - { - /** - * country code - StringType(SIZE(2)) - */ - public static readonly DerObjectIdentifier C = new DerObjectIdentifier("2.5.4.6"); - - /** - * organization - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier O = new DerObjectIdentifier("2.5.4.10"); - - /** - * organizational unit name - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier OU = new DerObjectIdentifier("2.5.4.11"); - - /** - * Title - */ - public static readonly DerObjectIdentifier T = new DerObjectIdentifier("2.5.4.12"); - - /** - * common name - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier CN = new DerObjectIdentifier("2.5.4.3"); - - /** - * street - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier Street = new DerObjectIdentifier("2.5.4.9"); - - /** - * device serial number name - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier SerialNumber = new DerObjectIdentifier("2.5.4.5"); - - /** - * locality name - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier L = new DerObjectIdentifier("2.5.4.7"); - - /** - * state, or province name - StringType(SIZE(1..64)) - */ - public static readonly DerObjectIdentifier ST = new DerObjectIdentifier("2.5.4.8"); - - /** - * Naming attributes of type X520name - */ - public static readonly DerObjectIdentifier Surname = new DerObjectIdentifier("2.5.4.4"); - public static readonly DerObjectIdentifier GivenName = new DerObjectIdentifier("2.5.4.42"); - public static readonly DerObjectIdentifier Initials = new DerObjectIdentifier("2.5.4.43"); - public static readonly DerObjectIdentifier Generation = new DerObjectIdentifier("2.5.4.44"); - public static readonly DerObjectIdentifier UniqueIdentifier = new DerObjectIdentifier("2.5.4.45"); - - /** - * businessCategory - DirectoryString(SIZE(1..128) - */ - public static readonly DerObjectIdentifier BusinessCategory = new DerObjectIdentifier( - "2.5.4.15"); - - /** - * postalCode - DirectoryString(SIZE(1..40) - */ - public static readonly DerObjectIdentifier PostalCode = new DerObjectIdentifier( - "2.5.4.17"); - - /** - * dnQualifier - DirectoryString(SIZE(1..64) - */ - public static readonly DerObjectIdentifier DnQualifier = new DerObjectIdentifier( - "2.5.4.46"); - - /** - * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64) - */ - public static readonly DerObjectIdentifier Pseudonym = new DerObjectIdentifier( - "2.5.4.65"); - - /** - * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z - */ - public static readonly DerObjectIdentifier DateOfBirth = new DerObjectIdentifier( - "1.3.6.1.5.5.7.9.1"); - - /** - * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128) - */ - public static readonly DerObjectIdentifier PlaceOfBirth = new DerObjectIdentifier( - "1.3.6.1.5.5.7.9.2"); - - /** - * RFC 3039 DateOfBirth - PrintableString (SIZE(1)) -- "M", "F", "m" or "f" - */ - public static readonly DerObjectIdentifier Gender = new DerObjectIdentifier( - "1.3.6.1.5.5.7.9.3"); - - /** - * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 - * codes only - */ - public static readonly DerObjectIdentifier CountryOfCitizenship = new DerObjectIdentifier( - "1.3.6.1.5.5.7.9.4"); - - /** - * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 - * codes only - */ - public static readonly DerObjectIdentifier CountryOfResidence = new DerObjectIdentifier( - "1.3.6.1.5.5.7.9.5"); - - /** - * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64) - */ - public static readonly DerObjectIdentifier NameAtBirth = new DerObjectIdentifier("1.3.36.8.3.14"); - - /** - * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF - * DirectoryString(SIZE(1..30)) - */ - public static readonly DerObjectIdentifier PostalAddress = new DerObjectIdentifier("2.5.4.16"); - - /** - * id-at-telephoneNumber - */ - public static readonly DerObjectIdentifier TelephoneNumber = new DerObjectIdentifier("2.5.4.20"); - - /** - * Email address (RSA PKCS#9 extension) - IA5String. - *

Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.

- */ - public static readonly DerObjectIdentifier EmailAddress = PkcsObjectIdentifiers.Pkcs9AtEmailAddress; - - /** - * more from PKCS#9 - */ - public static readonly DerObjectIdentifier UnstructuredName = PkcsObjectIdentifiers.Pkcs9AtUnstructuredName; - public static readonly DerObjectIdentifier UnstructuredAddress = PkcsObjectIdentifiers.Pkcs9AtUnstructuredAddress; - - /** - * email address in Verisign certificates - */ - public static readonly DerObjectIdentifier E = EmailAddress; - - /* - * others... - */ - public static readonly DerObjectIdentifier DC = new DerObjectIdentifier("0.9.2342.19200300.100.1.25"); - - /** - * LDAP User id. - */ - public static readonly DerObjectIdentifier UID = new DerObjectIdentifier("0.9.2342.19200300.100.1.1"); - - /** - * determines whether or not strings should be processed and printed - * from back to front. - */ -// public static bool DefaultReverse = false; - public static bool DefaultReverse - { - get { return defaultReverse[0]; } - set { defaultReverse[0] = value; } - } - - private static readonly bool[] defaultReverse = { false }; - - /** - * default look up table translating OID values into their common symbols following - * the convention in RFC 2253 with a few extras - */ - public static readonly Hashtable DefaultSymbols = new Hashtable(); - - /** - * look up table translating OID values into their common symbols following the convention in RFC 2253 - */ - public static readonly Hashtable RFC2253Symbols = new Hashtable(); - - /** - * look up table translating OID values into their common symbols following the convention in RFC 1779 - * - */ - public static readonly Hashtable RFC1779Symbols = new Hashtable(); - - /** - * look up table translating common symbols into their OIDS. - */ - public static readonly Hashtable DefaultLookup = new Hashtable(); - - /** - * look up table translating OID values into their common symbols. - */ - [Obsolete("Use 'DefaultSymbols' instead")] - public static readonly Hashtable OIDLookup = DefaultSymbols; - - /** - * look up table translating string values into their OIDS - - * this static is scheduled for deletion - */ - [Obsolete("Use 'DefaultLookup' instead")] - public static readonly Hashtable SymbolLookup = DefaultLookup; - - static X509Name() - { - DefaultSymbols.Add(C, "C"); - DefaultSymbols.Add(O, "O"); - DefaultSymbols.Add(T, "T"); - DefaultSymbols.Add(OU, "OU"); - DefaultSymbols.Add(CN, "CN"); - DefaultSymbols.Add(L, "L"); - DefaultSymbols.Add(ST, "ST"); - DefaultSymbols.Add(SerialNumber, "SERIALNUMBER"); - DefaultSymbols.Add(EmailAddress, "E"); - DefaultSymbols.Add(DC, "DC"); - DefaultSymbols.Add(UID, "UID"); - DefaultSymbols.Add(Street, "STREET"); - DefaultSymbols.Add(Surname, "SURNAME"); - DefaultSymbols.Add(GivenName, "GIVENNAME"); - DefaultSymbols.Add(Initials, "INITIALS"); - DefaultSymbols.Add(Generation, "GENERATION"); - DefaultSymbols.Add(UnstructuredAddress, "unstructuredAddress"); - DefaultSymbols.Add(UnstructuredName, "unstructuredName"); - DefaultSymbols.Add(UniqueIdentifier, "UniqueIdentifier"); - DefaultSymbols.Add(DnQualifier, "DN"); - DefaultSymbols.Add(Pseudonym, "Pseudonym"); - DefaultSymbols.Add(PostalAddress, "PostalAddress"); - DefaultSymbols.Add(NameAtBirth, "NameAtBirth"); - DefaultSymbols.Add(CountryOfCitizenship, "CountryOfCitizenship"); - DefaultSymbols.Add(CountryOfResidence, "CountryOfResidence"); - DefaultSymbols.Add(Gender, "Gender"); - DefaultSymbols.Add(PlaceOfBirth, "PlaceOfBirth"); - DefaultSymbols.Add(DateOfBirth, "DateOfBirth"); - DefaultSymbols.Add(PostalCode, "PostalCode"); - DefaultSymbols.Add(BusinessCategory, "BusinessCategory"); - DefaultSymbols.Add(TelephoneNumber, "TelephoneNumber"); - - RFC2253Symbols.Add(C, "C"); - RFC2253Symbols.Add(O, "O"); - RFC2253Symbols.Add(OU, "OU"); - RFC2253Symbols.Add(CN, "CN"); - RFC2253Symbols.Add(L, "L"); - RFC2253Symbols.Add(ST, "ST"); - RFC2253Symbols.Add(Street, "STREET"); - RFC2253Symbols.Add(DC, "DC"); - RFC2253Symbols.Add(UID, "UID"); - - RFC1779Symbols.Add(C, "C"); - RFC1779Symbols.Add(O, "O"); - RFC1779Symbols.Add(OU, "OU"); - RFC1779Symbols.Add(CN, "CN"); - RFC1779Symbols.Add(L, "L"); - RFC1779Symbols.Add(ST, "ST"); - RFC1779Symbols.Add(Street, "STREET"); - - DefaultLookup.Add("c", C); - DefaultLookup.Add("o", O); - DefaultLookup.Add("t", T); - DefaultLookup.Add("ou", OU); - DefaultLookup.Add("cn", CN); - DefaultLookup.Add("l", L); - DefaultLookup.Add("st", ST); - DefaultLookup.Add("serialnumber", SerialNumber); - DefaultLookup.Add("street", Street); - DefaultLookup.Add("emailaddress", E); - DefaultLookup.Add("dc", DC); - DefaultLookup.Add("e", E); - DefaultLookup.Add("uid", UID); - DefaultLookup.Add("surname", Surname); - DefaultLookup.Add("givenname", GivenName); - DefaultLookup.Add("initials", Initials); - DefaultLookup.Add("generation", Generation); - DefaultLookup.Add("unstructuredaddress", UnstructuredAddress); - DefaultLookup.Add("unstructuredname", UnstructuredName); - DefaultLookup.Add("uniqueidentifier", UniqueIdentifier); - DefaultLookup.Add("dn", DnQualifier); - DefaultLookup.Add("pseudonym", Pseudonym); - DefaultLookup.Add("postaladdress", PostalAddress); - DefaultLookup.Add("nameofbirth", NameAtBirth); - DefaultLookup.Add("countryofcitizenship", CountryOfCitizenship); - DefaultLookup.Add("countryofresidence", CountryOfResidence); - DefaultLookup.Add("gender", Gender); - DefaultLookup.Add("placeofbirth", PlaceOfBirth); - DefaultLookup.Add("dateofbirth", DateOfBirth); - DefaultLookup.Add("postalcode", PostalCode); - DefaultLookup.Add("businesscategory", BusinessCategory); - DefaultLookup.Add("telephonenumber", TelephoneNumber); - } - - private readonly ArrayList ordering = new ArrayList(); - private readonly X509NameEntryConverter converter; - - private ArrayList values = new ArrayList(); - private ArrayList added = new ArrayList(); - private Asn1Sequence seq; - - /** - * Return a X509Name based on the passed in tagged object. - * - * @param obj tag object holding name. - * @param explicitly true if explicitly tagged false otherwise. - * @return the X509Name - */ - public static X509Name GetInstance( - Asn1TaggedObject obj, - bool explicitly) - { - return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); - } - - public static X509Name GetInstance( - object obj) - { - if (obj == null || obj is X509Name) - { - return (X509Name) obj; - } - - if (obj is Asn1Sequence) - { - return new X509Name((Asn1Sequence) obj); - } - - throw new ArgumentException("unknown object in factory: " + obj.GetType().Name); - } - - /** - * Constructor from Asn1Sequence - * - * the principal will be a list of constructed sets, each containing an (OID, string) pair. - */ - protected X509Name( - Asn1Sequence seq) - { - this.seq = seq; - - foreach (Asn1Encodable asn1Obj in seq) - { - Asn1Set asn1Set = Asn1Set.GetInstance(asn1Obj.ToAsn1Object()); - - for (int i = 0; i < asn1Set.Count; i++) - { - Asn1Sequence s = Asn1Sequence.GetInstance(asn1Set[i].ToAsn1Object()); - - if (s.Count != 2) - throw new ArgumentException("badly sized pair"); - - ordering.Add(DerObjectIdentifier.GetInstance(s[0].ToAsn1Object())); - - Asn1Object derValue = s[1].ToAsn1Object(); - if (derValue is IAsn1String && !(derValue is DerUniversalString)) - { - string v = ((IAsn1String)derValue).GetString(); - if (v.StartsWith("#")) - { - v = "\\" + v; - } - - values.Add(v); - } - else - { - byte[] hex = Hex.Encode(derValue.GetEncoded()); - values.Add("#" + Encoding.ASCII.GetString(hex, 0, hex.Length)); - } - - added.Add(i != 0); - } - } - } - - /** - * Constructor from a table of attributes with ordering. - *

- * it's is assumed the table contains OID/string pairs, and the contents - * of the table are copied into an internal table as part of the - * construction process. The ordering ArrayList should contain the OIDs - * in the order they are meant to be encoded or printed in ToString.

- */ - public X509Name( - ArrayList ordering, - Hashtable attributes) - : this(ordering, attributes, new X509DefaultEntryConverter()) - { - } - - /** - * Constructor from a table of attributes with ordering. - *

- * it's is assumed the table contains OID/string pairs, and the contents - * of the table are copied into an internal table as part of the - * construction process. The ordering ArrayList should contain the OIDs - * in the order they are meant to be encoded or printed in ToString.

- *

- * The passed in converter will be used to convert the strings into their - * ASN.1 counterparts.

- */ - public X509Name( - ArrayList ordering, - Hashtable attributes, - X509NameEntryConverter converter) - { - this.converter = converter; - - foreach (DerObjectIdentifier oid in ordering) - { - object attribute = attributes[oid]; - if (attribute == null) - { - throw new ArgumentException("No attribute for object id - " + oid + " - passed to distinguished name"); - } - - this.ordering.Add(oid); - this.added.Add(false); - this.values.Add(attribute); // copy the hash table - } - } - - /** - * Takes two vectors one of the oids and the other of the values. - */ - public X509Name( - ArrayList oids, - ArrayList values) - : this(oids, values, new X509DefaultEntryConverter()) - { - } - - /** - * Takes two vectors one of the oids and the other of the values. - *

- * The passed in converter will be used to convert the strings into their - * ASN.1 counterparts.

- */ - public X509Name( - ArrayList oids, - ArrayList values, - X509NameEntryConverter converter) - { - this.converter = converter; - - if (oids.Count != values.Count) - { - throw new ArgumentException("'oids' must be same length as 'values'."); - } - - for (int i = 0; i < oids.Count; i++) - { - this.ordering.Add(oids[i]); - this.values.Add(values[i]); - this.added.Add(false); - } - } - -// private static bool IsEncoded( -// string s) -// { -// return s.StartsWith("#"); -// } - - /** - * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or - * some such, converting it into an ordered set of name attributes. - */ - public X509Name( - string dirName) - : this(DefaultReverse, DefaultLookup, dirName) - { - } - - /** - * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or - * some such, converting it into an ordered set of name attributes with each - * string value being converted to its associated ASN.1 type using the passed - * in converter. - */ - public X509Name( - string dirName, - X509NameEntryConverter converter) - : this(DefaultReverse, DefaultLookup, dirName, converter) - { - } - - /** - * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or - * some such, converting it into an ordered set of name attributes. If reverse - * is true, create the encoded version of the sequence starting from the - * last element in the string. - */ - public X509Name( - bool reverse, - string dirName) - : this(reverse, DefaultLookup, dirName) - { - } - - /** - * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or - * some such, converting it into an ordered set of name attributes with each - * string value being converted to its associated ASN.1 type using the passed - * in converter. If reverse is true the ASN.1 sequence representing the DN will - * be built by starting at the end of the string, rather than the start. - */ - public X509Name( - bool reverse, - string dirName, - X509NameEntryConverter converter) - : this(reverse, DefaultLookup, dirName, converter) - { - } - - /** - * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or - * some such, converting it into an ordered set of name attributes. lookUp - * should provide a table of lookups, indexed by lowercase only strings and - * yielding a DerObjectIdentifier, other than that OID. and numeric oids - * will be processed automatically. - *
- * If reverse is true, create the encoded version of the sequence - * starting from the last element in the string. - * @param reverse true if we should start scanning from the end (RFC 2553). - * @param lookUp table of names and their oids. - * @param dirName the X.500 string to be parsed. - */ - public X509Name( - bool reverse, - Hashtable lookUp, - string dirName) - : this(reverse, lookUp, dirName, new X509DefaultEntryConverter()) - { - } - - private DerObjectIdentifier DecodeOid( - string name, - IDictionary lookUp) - { - if (name.ToUpper(CultureInfo.InvariantCulture).StartsWith("OID.")) - { - return new DerObjectIdentifier(name.Substring(4)); - } - else if (name[0] >= '0' && name[0] <= '9') - { - return new DerObjectIdentifier(name); - } - - DerObjectIdentifier oid = (DerObjectIdentifier)lookUp[name.ToLower(CultureInfo.InvariantCulture)]; - if (oid == null) - { - throw new ArgumentException("Unknown object id - " + name + " - passed to distinguished name"); - } - - return oid; - } - - /** - * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or - * some such, converting it into an ordered set of name attributes. lookUp - * should provide a table of lookups, indexed by lowercase only strings and - * yielding a DerObjectIdentifier, other than that OID. and numeric oids - * will be processed automatically. The passed in converter is used to convert the - * string values to the right of each equals sign to their ASN.1 counterparts. - *
- * @param reverse true if we should start scanning from the end, false otherwise. - * @param lookUp table of names and oids. - * @param dirName the string dirName - * @param converter the converter to convert string values into their ASN.1 equivalents - */ - public X509Name( - bool reverse, - IDictionary lookUp, - string dirName, - X509NameEntryConverter converter) - { - this.converter = converter; - X509NameTokenizer nTok = new X509NameTokenizer(dirName); - - while (nTok.HasMoreTokens()) - { - string token = nTok.NextToken(); - int index = token.IndexOf('='); - - if (index == -1) - { - throw new ArgumentException("badly formated directory string"); - } - - string name = token.Substring(0, index); - string value = token.Substring(index + 1); - DerObjectIdentifier oid = DecodeOid(name, lookUp); - - if (value.IndexOf('+') > 0) - { - X509NameTokenizer vTok = new X509NameTokenizer(value, '+'); - string v = vTok.NextToken(); - - this.ordering.Add(oid); - this.values.Add(v); - this.added.Add(false); - - while (vTok.HasMoreTokens()) - { - string sv = vTok.NextToken(); - int ndx = sv.IndexOf('='); - - string nm = sv.Substring(0, ndx); - string vl = sv.Substring(ndx + 1); - this.ordering.Add(DecodeOid(nm, lookUp)); - this.values.Add(vl); - this.added.Add(true); - } - } - else - { - this.ordering.Add(oid); - this.values.Add(value); - this.added.Add(false); - } - } - - if (reverse) - { -// this.ordering.Reverse(); -// this.values.Reverse(); -// this.added.Reverse(); - ArrayList o = new ArrayList(); - ArrayList v = new ArrayList(); - ArrayList a = new ArrayList(); - int count = 1; - - for (int i = 0; i < this.ordering.Count; i++) - { - if (!((bool) this.added[i])) - { - count = 0; - } - - int index = count++; - - o.Insert(index, this.ordering[i]); - v.Insert(index, this.values[i]); - a.Insert(index, this.added[i]); - } - - this.ordering = o; - this.values = v; - this.added = a; - } - } - - /** - * return an ArrayList of the oids in the name, in the order they were found. - */ - public ArrayList GetOids() - { - return (ArrayList) ordering.Clone(); - } - - /** - * return an ArrayList of the values found in the name, in the order they - * were found. - */ - public ArrayList GetValues() - { - return (ArrayList) values.Clone(); - } - - /** - * return an ArrayList of the values found in the name, in the order they - * were found, with the DN label corresponding to passed in oid. - */ - public ArrayList GetValues( - DerObjectIdentifier oid) - { - ArrayList v = new ArrayList(); - - for (int i = 0; i != values.Count; i++) - { - if (ordering[i].Equals(oid)) - { - string val = (string)values[i]; - - if (val.StartsWith("\\#")) - { - val = val.Substring(1); - } - - v.Add(val); - } - } - - return v; - } - - public override Asn1Object ToAsn1Object() - { - if (seq == null) - { - Asn1EncodableVector vec = new Asn1EncodableVector(); - Asn1EncodableVector sVec = new Asn1EncodableVector(); - DerObjectIdentifier lstOid = null; - - for (int i = 0; i != ordering.Count; i++) - { - DerObjectIdentifier oid = (DerObjectIdentifier)ordering[i]; - string str = (string)values[i]; - - if (lstOid == null - || ((bool)this.added[i])) - { - } - else - { - vec.Add(new DerSet(sVec)); - sVec = new Asn1EncodableVector(); - } - - sVec.Add( - new DerSequence( - oid, - converter.GetConvertedValue(oid, str))); - - lstOid = oid; - } - - vec.Add(new DerSet(sVec)); - - seq = new DerSequence(vec); - } - - return seq; - } - - [Obsolete("Use 'Equivalent(X509Name, int)' instead")] - public bool Equals( - X509Name other, - bool inOrder) - { - return Equivalent(other, inOrder); - } - - /// The X509Name object to test equivalency against. - /// If true, the order of elements must be the same, - /// as well as the values associated with each element. - public bool Equivalent( - X509Name other, - bool inOrder) - { - if (!inOrder) - return this.Equivalent(other); - - if (other == null) - return false; - - if (other == this) - return true; - - int orderingSize = ordering.Count; - - if (orderingSize != other.ordering.Count) - return false; - - for (int i = 0; i < orderingSize; i++) - { - DerObjectIdentifier oid = (DerObjectIdentifier) ordering[i]; - DerObjectIdentifier oOid = (DerObjectIdentifier) other.ordering[i]; - - if (!oid.Equals(oOid)) - return false; - - string val = (string) values[i]; - string oVal = (string) other.values[i]; - - if (!equivalentStrings(val, oVal)) - return false; - } - - return true; - } - - [Obsolete("Use 'Equivalent(X509Name)' instead")] - public bool Equals( - X509Name other) - { - return Equivalent(other); - } - - /** - * test for equivalence - note: case is ignored. - */ - public bool Equivalent( - X509Name other) - { - if (other == null) - return false; - - if (other == this) - return true; - - int orderingSize = ordering.Count; - - if (orderingSize != other.ordering.Count) - { - return false; - } - - bool[] indexes = new bool[orderingSize]; - int start, end, delta; - - if (ordering[0].Equals(other.ordering[0])) // guess forward - { - start = 0; - end = orderingSize; - delta = 1; - } - else // guess reversed - most common problem - { - start = orderingSize - 1; - end = -1; - delta = -1; - } - - for (int i = start; i != end; i += delta) - { - bool found = false; - DerObjectIdentifier oid = (DerObjectIdentifier)ordering[i]; - string value = (string)values[i]; - - for (int j = 0; j < orderingSize; j++) - { - if (indexes[j]) - { - continue; - } - - DerObjectIdentifier oOid = (DerObjectIdentifier)other.ordering[j]; - - if (oid.Equals(oOid)) - { - string oValue = (string)other.values[j]; - - if (equivalentStrings(value, oValue)) - { - indexes[j] = true; - found = true; - break; - } - } - } - - if (!found) - { - return false; - } - } - - return true; - } - - private static bool equivalentStrings( - string s1, - string s2) - { - string v1 = canonicalize(s1); - string v2 = canonicalize(s2); - - if (!v1.Equals(v2)) - { - v1 = stripInternalSpaces(v1); - v2 = stripInternalSpaces(v2); - - if (!v1.Equals(v2)) - { - return false; - } - } - - return true; - } - - private static string canonicalize( - string s) - { - string v = s.ToLower(CultureInfo.InvariantCulture).Trim(); - - if (v.StartsWith("#")) - { - Asn1Object obj = decodeObject(v); - - if (obj is IAsn1String) - { - v = ((IAsn1String)obj).GetString().ToLower(CultureInfo.InvariantCulture).Trim(); - } - } - - return v; - } - - private static Asn1Object decodeObject( - string v) - { - try - { - return Asn1Object.FromByteArray(Hex.Decode(v.Substring(1))); - } - catch (IOException e) - { - throw new InvalidOperationException("unknown encoding in name: " + e.Message, e); - } - } - - private static string stripInternalSpaces( - string str) - { - StringBuilder res = new StringBuilder(); - - if (str.Length != 0) - { - char c1 = str[0]; - - res.Append(c1); - - for (int k = 1; k < str.Length; k++) - { - char c2 = str[k]; - if (!(c1 == ' ' && c2 == ' ')) - { - res.Append(c2); - } - c1 = c2; - } - } - - return res.ToString(); - } - - private void AppendValue( - StringBuilder buf, - Hashtable oidSymbols, - DerObjectIdentifier oid, - string val) - { - string sym = (string) oidSymbols[oid]; - - if (sym != null) - { - buf.Append(sym); - } - else - { - buf.Append(oid.Id); - } - - buf.Append('='); - - int index = buf.Length; - - buf.Append(val); - - int end = buf.Length; - - if (val.StartsWith("\\#")) - { - index += 2; - } - - while (index != end) - { - if ((buf[index] == ',') - || (buf[index] == '"') - || (buf[index] == '\\') - || (buf[index] == '+') - || (buf[index] == '<') - || (buf[index] == '>') - || (buf[index] == ';')) - { - buf.Insert(index++, "\\"); - end++; - } - - index++; - } - } - - /** - * convert the structure to a string - if reverse is true the - * oids and values are listed out starting with the last element - * in the sequence (ala RFC 2253), otherwise the string will begin - * with the first element of the structure. If no string definition - * for the oid is found in oidSymbols the string value of the oid is - * added. Two standard symbol tables are provided DefaultSymbols, and - * RFC2253Symbols as part of this class. - * - * @param reverse if true start at the end of the sequence and work back. - * @param oidSymbols look up table strings for oids. - */ - public string ToString( - bool reverse, - Hashtable oidSymbols) - { - ArrayList components = new ArrayList(); - - StringBuilder ava = null; - - for (int i = 0; i < ordering.Count; i++) - { - if ((bool) added[i]) - { - ava.Append('+'); - AppendValue(ava, oidSymbols, - (DerObjectIdentifier)ordering[i], - (string)values[i]); - } - else - { - ava = new StringBuilder(); - AppendValue(ava, oidSymbols, - (DerObjectIdentifier)ordering[i], - (string)values[i]); - components.Add(ava); - } - } - - if (reverse) - { - components.Reverse(); - } - - StringBuilder buf = new StringBuilder(); - - if (components.Count > 0) - { - buf.Append(components[0].ToString()); - - for (int i = 1; i < components.Count; ++i) - { - buf.Append(','); - buf.Append(components[i].ToString()); - } - } - - return buf.ToString(); - } - - public override string ToString() - { - return ToString(DefaultReverse, DefaultSymbols); - } - } -} +using System; +using System.Collections; +using System.Globalization; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Asn1.X509 +{ + /** + *
+    *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+    *
+    *     RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
+    *
+    *     AttributeTypeAndValue ::= SEQUENCE {
+    *                                   type  OBJECT IDENTIFIER,
+    *                                   value ANY }
+    * 
+ */ + public class X509Name + : Asn1Encodable + { + /** + * country code - StringType(SIZE(2)) + */ + public static readonly DerObjectIdentifier C = new DerObjectIdentifier("2.5.4.6"); + + /** + * organization - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier O = new DerObjectIdentifier("2.5.4.10"); + + /** + * organizational unit name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier OU = new DerObjectIdentifier("2.5.4.11"); + + /** + * Title + */ + public static readonly DerObjectIdentifier T = new DerObjectIdentifier("2.5.4.12"); + + /** + * common name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier CN = new DerObjectIdentifier("2.5.4.3"); + + /** + * street - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier Street = new DerObjectIdentifier("2.5.4.9"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier SerialNumber = new DerObjectIdentifier("2.5.4.5"); + + /** + * locality name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier L = new DerObjectIdentifier("2.5.4.7"); + + /** + * state, or province name - StringType(SIZE(1..64)) + */ + public static readonly DerObjectIdentifier ST = new DerObjectIdentifier("2.5.4.8"); + + /** + * Naming attributes of type X520name + */ + public static readonly DerObjectIdentifier Surname = new DerObjectIdentifier("2.5.4.4"); + public static readonly DerObjectIdentifier GivenName = new DerObjectIdentifier("2.5.4.42"); + public static readonly DerObjectIdentifier Initials = new DerObjectIdentifier("2.5.4.43"); + public static readonly DerObjectIdentifier Generation = new DerObjectIdentifier("2.5.4.44"); + public static readonly DerObjectIdentifier UniqueIdentifier = new DerObjectIdentifier("2.5.4.45"); + + /** + * businessCategory - DirectoryString(SIZE(1..128) + */ + public static readonly DerObjectIdentifier BusinessCategory = new DerObjectIdentifier( + "2.5.4.15"); + + /** + * postalCode - DirectoryString(SIZE(1..40) + */ + public static readonly DerObjectIdentifier PostalCode = new DerObjectIdentifier( + "2.5.4.17"); + + /** + * dnQualifier - DirectoryString(SIZE(1..64) + */ + public static readonly DerObjectIdentifier DnQualifier = new DerObjectIdentifier( + "2.5.4.46"); + + /** + * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64) + */ + public static readonly DerObjectIdentifier Pseudonym = new DerObjectIdentifier( + "2.5.4.65"); + + /** + * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z + */ + public static readonly DerObjectIdentifier DateOfBirth = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.1"); + + /** + * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128) + */ + public static readonly DerObjectIdentifier PlaceOfBirth = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.2"); + + /** + * RFC 3039 DateOfBirth - PrintableString (SIZE(1)) -- "M", "F", "m" or "f" + */ + public static readonly DerObjectIdentifier Gender = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.3"); + + /** + * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static readonly DerObjectIdentifier CountryOfCitizenship = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.4"); + + /** + * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static readonly DerObjectIdentifier CountryOfResidence = new DerObjectIdentifier( + "1.3.6.1.5.5.7.9.5"); + + /** + * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64) + */ + public static readonly DerObjectIdentifier NameAtBirth = new DerObjectIdentifier("1.3.36.8.3.14"); + + /** + * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF + * DirectoryString(SIZE(1..30)) + */ + public static readonly DerObjectIdentifier PostalAddress = new DerObjectIdentifier("2.5.4.16"); + + /** + * id-at-telephoneNumber + */ + public static readonly DerObjectIdentifier TelephoneNumber = X509ObjectIdentifiers.id_at_telephoneNumber; + + /** + * id-at-name + */ + public static readonly DerObjectIdentifier Name = X509ObjectIdentifiers.id_at_name; + + /** + * Email address (RSA PKCS#9 extension) - IA5String. + *

Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.

+ */ + public static readonly DerObjectIdentifier EmailAddress = PkcsObjectIdentifiers.Pkcs9AtEmailAddress; + + /** + * more from PKCS#9 + */ + public static readonly DerObjectIdentifier UnstructuredName = PkcsObjectIdentifiers.Pkcs9AtUnstructuredName; + public static readonly DerObjectIdentifier UnstructuredAddress = PkcsObjectIdentifiers.Pkcs9AtUnstructuredAddress; + + /** + * email address in Verisign certificates + */ + public static readonly DerObjectIdentifier E = EmailAddress; + + /* + * others... + */ + public static readonly DerObjectIdentifier DC = new DerObjectIdentifier("0.9.2342.19200300.100.1.25"); + + /** + * LDAP User id. + */ + public static readonly DerObjectIdentifier UID = new DerObjectIdentifier("0.9.2342.19200300.100.1.1"); + + /** + * determines whether or not strings should be processed and printed + * from back to front. + */ +// public static bool DefaultReverse = false; + public static bool DefaultReverse + { + get { return defaultReverse[0]; } + set { defaultReverse[0] = value; } + } + + private static readonly bool[] defaultReverse = { false }; + + /** + * default look up table translating OID values into their common symbols following + * the convention in RFC 2253 with a few extras + */ + public static readonly Hashtable DefaultSymbols = new Hashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 2253 + */ + public static readonly Hashtable RFC2253Symbols = new Hashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 1779 + * + */ + public static readonly Hashtable RFC1779Symbols = new Hashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + public static readonly Hashtable DefaultLookup = new Hashtable(); + + /** + * look up table translating OID values into their common symbols. + */ + [Obsolete("Use 'DefaultSymbols' instead")] + public static readonly Hashtable OIDLookup = DefaultSymbols; + + /** + * look up table translating string values into their OIDS - + * this static is scheduled for deletion + */ + [Obsolete("Use 'DefaultLookup' instead")] + public static readonly Hashtable SymbolLookup = DefaultLookup; + + static X509Name() + { + DefaultSymbols.Add(C, "C"); + DefaultSymbols.Add(O, "O"); + DefaultSymbols.Add(T, "T"); + DefaultSymbols.Add(OU, "OU"); + DefaultSymbols.Add(CN, "CN"); + DefaultSymbols.Add(L, "L"); + DefaultSymbols.Add(ST, "ST"); + DefaultSymbols.Add(SerialNumber, "SERIALNUMBER"); + DefaultSymbols.Add(EmailAddress, "E"); + DefaultSymbols.Add(DC, "DC"); + DefaultSymbols.Add(UID, "UID"); + DefaultSymbols.Add(Street, "STREET"); + DefaultSymbols.Add(Surname, "SURNAME"); + DefaultSymbols.Add(GivenName, "GIVENNAME"); + DefaultSymbols.Add(Initials, "INITIALS"); + DefaultSymbols.Add(Generation, "GENERATION"); + DefaultSymbols.Add(UnstructuredAddress, "unstructuredAddress"); + DefaultSymbols.Add(UnstructuredName, "unstructuredName"); + DefaultSymbols.Add(UniqueIdentifier, "UniqueIdentifier"); + DefaultSymbols.Add(DnQualifier, "DN"); + DefaultSymbols.Add(Pseudonym, "Pseudonym"); + DefaultSymbols.Add(PostalAddress, "PostalAddress"); + DefaultSymbols.Add(NameAtBirth, "NameAtBirth"); + DefaultSymbols.Add(CountryOfCitizenship, "CountryOfCitizenship"); + DefaultSymbols.Add(CountryOfResidence, "CountryOfResidence"); + DefaultSymbols.Add(Gender, "Gender"); + DefaultSymbols.Add(PlaceOfBirth, "PlaceOfBirth"); + DefaultSymbols.Add(DateOfBirth, "DateOfBirth"); + DefaultSymbols.Add(PostalCode, "PostalCode"); + DefaultSymbols.Add(BusinessCategory, "BusinessCategory"); + DefaultSymbols.Add(TelephoneNumber, "TelephoneNumber"); + + RFC2253Symbols.Add(C, "C"); + RFC2253Symbols.Add(O, "O"); + RFC2253Symbols.Add(OU, "OU"); + RFC2253Symbols.Add(CN, "CN"); + RFC2253Symbols.Add(L, "L"); + RFC2253Symbols.Add(ST, "ST"); + RFC2253Symbols.Add(Street, "STREET"); + RFC2253Symbols.Add(DC, "DC"); + RFC2253Symbols.Add(UID, "UID"); + + RFC1779Symbols.Add(C, "C"); + RFC1779Symbols.Add(O, "O"); + RFC1779Symbols.Add(OU, "OU"); + RFC1779Symbols.Add(CN, "CN"); + RFC1779Symbols.Add(L, "L"); + RFC1779Symbols.Add(ST, "ST"); + RFC1779Symbols.Add(Street, "STREET"); + + DefaultLookup.Add("c", C); + DefaultLookup.Add("o", O); + DefaultLookup.Add("t", T); + DefaultLookup.Add("ou", OU); + DefaultLookup.Add("cn", CN); + DefaultLookup.Add("l", L); + DefaultLookup.Add("st", ST); + DefaultLookup.Add("serialnumber", SerialNumber); + DefaultLookup.Add("street", Street); + DefaultLookup.Add("emailaddress", E); + DefaultLookup.Add("dc", DC); + DefaultLookup.Add("e", E); + DefaultLookup.Add("uid", UID); + DefaultLookup.Add("surname", Surname); + DefaultLookup.Add("givenname", GivenName); + DefaultLookup.Add("initials", Initials); + DefaultLookup.Add("generation", Generation); + DefaultLookup.Add("unstructuredaddress", UnstructuredAddress); + DefaultLookup.Add("unstructuredname", UnstructuredName); + DefaultLookup.Add("uniqueidentifier", UniqueIdentifier); + DefaultLookup.Add("dn", DnQualifier); + DefaultLookup.Add("pseudonym", Pseudonym); + DefaultLookup.Add("postaladdress", PostalAddress); + DefaultLookup.Add("nameofbirth", NameAtBirth); + DefaultLookup.Add("countryofcitizenship", CountryOfCitizenship); + DefaultLookup.Add("countryofresidence", CountryOfResidence); + DefaultLookup.Add("gender", Gender); + DefaultLookup.Add("placeofbirth", PlaceOfBirth); + DefaultLookup.Add("dateofbirth", DateOfBirth); + DefaultLookup.Add("postalcode", PostalCode); + DefaultLookup.Add("businesscategory", BusinessCategory); + DefaultLookup.Add("telephonenumber", TelephoneNumber); + } + + private readonly ArrayList ordering = new ArrayList(); + private readonly X509NameEntryConverter converter; + + private ArrayList values = new ArrayList(); + private ArrayList added = new ArrayList(); + private Asn1Sequence seq; + + /** + * Return a X509Name based on the passed in tagged object. + * + * @param obj tag object holding name. + * @param explicitly true if explicitly tagged false otherwise. + * @return the X509Name + */ + public static X509Name GetInstance( + Asn1TaggedObject obj, + bool explicitly) + { + return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); + } + + public static X509Name GetInstance( + object obj) + { + if (obj == null || obj is X509Name) + { + return (X509Name) obj; + } + + if (obj is Asn1Sequence) + { + return new X509Name((Asn1Sequence) obj); + } + + throw new ArgumentException("unknown object in factory: " + obj.GetType().Name); + } + + /** + * Constructor from Asn1Sequence + * + * the principal will be a list of constructed sets, each containing an (OID, string) pair. + */ + protected X509Name( + Asn1Sequence seq) + { + this.seq = seq; + + foreach (Asn1Encodable asn1Obj in seq) + { + Asn1Set asn1Set = Asn1Set.GetInstance(asn1Obj.ToAsn1Object()); + + for (int i = 0; i < asn1Set.Count; i++) + { + Asn1Sequence s = Asn1Sequence.GetInstance(asn1Set[i].ToAsn1Object()); + + if (s.Count != 2) + throw new ArgumentException("badly sized pair"); + + ordering.Add(DerObjectIdentifier.GetInstance(s[0].ToAsn1Object())); + + Asn1Object derValue = s[1].ToAsn1Object(); + if (derValue is IAsn1String && !(derValue is DerUniversalString)) + { + string v = ((IAsn1String)derValue).GetString(); + if (v.StartsWith("#")) + { + v = "\\" + v; + } + + values.Add(v); + } + else + { + byte[] hex = Hex.Encode(derValue.GetEncoded()); + values.Add("#" + Encoding.ASCII.GetString(hex, 0, hex.Length)); + } + + added.Add(i != 0); + } + } + } + + /** + * Constructor from a table of attributes with ordering. + *

+ * it's is assumed the table contains OID/string pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. The ordering ArrayList should contain the OIDs + * in the order they are meant to be encoded or printed in ToString.

+ */ + public X509Name( + ArrayList ordering, + Hashtable attributes) + : this(ordering, attributes, new X509DefaultEntryConverter()) + { + } + + /** + * Constructor from a table of attributes with ordering. + *

+ * it's is assumed the table contains OID/string pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. The ordering ArrayList should contain the OIDs + * in the order they are meant to be encoded or printed in ToString.

+ *

+ * The passed in converter will be used to convert the strings into their + * ASN.1 counterparts.

+ */ + public X509Name( + ArrayList ordering, + Hashtable attributes, + X509NameEntryConverter converter) + { + this.converter = converter; + + foreach (DerObjectIdentifier oid in ordering) + { + object attribute = attributes[oid]; + if (attribute == null) + { + throw new ArgumentException("No attribute for object id - " + oid + " - passed to distinguished name"); + } + + this.ordering.Add(oid); + this.added.Add(false); + this.values.Add(attribute); // copy the hash table + } + } + + /** + * Takes two vectors one of the oids and the other of the values. + */ + public X509Name( + ArrayList oids, + ArrayList values) + : this(oids, values, new X509DefaultEntryConverter()) + { + } + + /** + * Takes two vectors one of the oids and the other of the values. + *

+ * The passed in converter will be used to convert the strings into their + * ASN.1 counterparts.

+ */ + public X509Name( + ArrayList oids, + ArrayList values, + X509NameEntryConverter converter) + { + this.converter = converter; + + if (oids.Count != values.Count) + { + throw new ArgumentException("'oids' must be same length as 'values'."); + } + + for (int i = 0; i < oids.Count; i++) + { + this.ordering.Add(oids[i]); + this.values.Add(values[i]); + this.added.Add(false); + } + } + +// private static bool IsEncoded( +// string s) +// { +// return s.StartsWith("#"); +// } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. + */ + public X509Name( + string dirName) + : this(DefaultReverse, DefaultLookup, dirName) + { + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes with each + * string value being converted to its associated ASN.1 type using the passed + * in converter. + */ + public X509Name( + string dirName, + X509NameEntryConverter converter) + : this(DefaultReverse, DefaultLookup, dirName, converter) + { + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. If reverse + * is true, create the encoded version of the sequence starting from the + * last element in the string. + */ + public X509Name( + bool reverse, + string dirName) + : this(reverse, DefaultLookup, dirName) + { + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes with each + * string value being converted to its associated ASN.1 type using the passed + * in converter. If reverse is true the ASN.1 sequence representing the DN will + * be built by starting at the end of the string, rather than the start. + */ + public X509Name( + bool reverse, + string dirName, + X509NameEntryConverter converter) + : this(reverse, DefaultLookup, dirName, converter) + { + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. lookUp + * should provide a table of lookups, indexed by lowercase only strings and + * yielding a DerObjectIdentifier, other than that OID. and numeric oids + * will be processed automatically. + *
+ * If reverse is true, create the encoded version of the sequence + * starting from the last element in the string. + * @param reverse true if we should start scanning from the end (RFC 2553). + * @param lookUp table of names and their oids. + * @param dirName the X.500 string to be parsed. + */ + public X509Name( + bool reverse, + Hashtable lookUp, + string dirName) + : this(reverse, lookUp, dirName, new X509DefaultEntryConverter()) + { + } + + private DerObjectIdentifier DecodeOid( + string name, + IDictionary lookUp) + { + if (name.ToUpper(CultureInfo.InvariantCulture).StartsWith("OID.")) + { + return new DerObjectIdentifier(name.Substring(4)); + } + else if (name[0] >= '0' && name[0] <= '9') + { + return new DerObjectIdentifier(name); + } + + DerObjectIdentifier oid = (DerObjectIdentifier)lookUp[name.ToLower(CultureInfo.InvariantCulture)]; + if (oid == null) + { + throw new ArgumentException("Unknown object id - " + name + " - passed to distinguished name"); + } + + return oid; + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. lookUp + * should provide a table of lookups, indexed by lowercase only strings and + * yielding a DerObjectIdentifier, other than that OID. and numeric oids + * will be processed automatically. The passed in converter is used to convert the + * string values to the right of each equals sign to their ASN.1 counterparts. + *
+ * @param reverse true if we should start scanning from the end, false otherwise. + * @param lookUp table of names and oids. + * @param dirName the string dirName + * @param converter the converter to convert string values into their ASN.1 equivalents + */ + public X509Name( + bool reverse, + IDictionary lookUp, + string dirName, + X509NameEntryConverter converter) + { + this.converter = converter; + X509NameTokenizer nTok = new X509NameTokenizer(dirName); + + while (nTok.HasMoreTokens()) + { + string token = nTok.NextToken(); + int index = token.IndexOf('='); + + if (index == -1) + { + throw new ArgumentException("badly formated directory string"); + } + + string name = token.Substring(0, index); + string value = token.Substring(index + 1); + DerObjectIdentifier oid = DecodeOid(name, lookUp); + + if (value.IndexOf('+') > 0) + { + X509NameTokenizer vTok = new X509NameTokenizer(value, '+'); + string v = vTok.NextToken(); + + this.ordering.Add(oid); + this.values.Add(v); + this.added.Add(false); + + while (vTok.HasMoreTokens()) + { + string sv = vTok.NextToken(); + int ndx = sv.IndexOf('='); + + string nm = sv.Substring(0, ndx); + string vl = sv.Substring(ndx + 1); + this.ordering.Add(DecodeOid(nm, lookUp)); + this.values.Add(vl); + this.added.Add(true); + } + } + else + { + this.ordering.Add(oid); + this.values.Add(value); + this.added.Add(false); + } + } + + if (reverse) + { +// this.ordering.Reverse(); +// this.values.Reverse(); +// this.added.Reverse(); + ArrayList o = new ArrayList(); + ArrayList v = new ArrayList(); + ArrayList a = new ArrayList(); + int count = 1; + + for (int i = 0; i < this.ordering.Count; i++) + { + if (!((bool) this.added[i])) + { + count = 0; + } + + int index = count++; + + o.Insert(index, this.ordering[i]); + v.Insert(index, this.values[i]); + a.Insert(index, this.added[i]); + } + + this.ordering = o; + this.values = v; + this.added = a; + } + } + + /** + * return an ArrayList of the oids in the name, in the order they were found. + */ + public ArrayList GetOids() + { + return (ArrayList) ordering.Clone(); + } + + /** + * return an ArrayList of the values found in the name, in the order they + * were found. + */ + public ArrayList GetValues() + { + return (ArrayList) values.Clone(); + } + + /** + * return an ArrayList of the values found in the name, in the order they + * were found, with the DN label corresponding to passed in oid. + */ + public ArrayList GetValues( + DerObjectIdentifier oid) + { + ArrayList v = new ArrayList(); + + for (int i = 0; i != values.Count; i++) + { + if (ordering[i].Equals(oid)) + { + string val = (string)values[i]; + + if (val.StartsWith("\\#")) + { + val = val.Substring(1); + } + + v.Add(val); + } + } + + return v; + } + + public override Asn1Object ToAsn1Object() + { + if (seq == null) + { + Asn1EncodableVector vec = new Asn1EncodableVector(); + Asn1EncodableVector sVec = new Asn1EncodableVector(); + DerObjectIdentifier lstOid = null; + + for (int i = 0; i != ordering.Count; i++) + { + DerObjectIdentifier oid = (DerObjectIdentifier)ordering[i]; + string str = (string)values[i]; + + if (lstOid == null + || ((bool)this.added[i])) + { + } + else + { + vec.Add(new DerSet(sVec)); + sVec = new Asn1EncodableVector(); + } + + sVec.Add( + new DerSequence( + oid, + converter.GetConvertedValue(oid, str))); + + lstOid = oid; + } + + vec.Add(new DerSet(sVec)); + + seq = new DerSequence(vec); + } + + return seq; + } + + [Obsolete("Use 'Equivalent(X509Name, int)' instead")] + public bool Equals( + X509Name other, + bool inOrder) + { + return Equivalent(other, inOrder); + } + + /// The X509Name object to test equivalency against. + /// If true, the order of elements must be the same, + /// as well as the values associated with each element. + public bool Equivalent( + X509Name other, + bool inOrder) + { + if (!inOrder) + return this.Equivalent(other); + + if (other == null) + return false; + + if (other == this) + return true; + + int orderingSize = ordering.Count; + + if (orderingSize != other.ordering.Count) + return false; + + for (int i = 0; i < orderingSize; i++) + { + DerObjectIdentifier oid = (DerObjectIdentifier) ordering[i]; + DerObjectIdentifier oOid = (DerObjectIdentifier) other.ordering[i]; + + if (!oid.Equals(oOid)) + return false; + + string val = (string) values[i]; + string oVal = (string) other.values[i]; + + if (!equivalentStrings(val, oVal)) + return false; + } + + return true; + } + + [Obsolete("Use 'Equivalent(X509Name)' instead")] + public bool Equals( + X509Name other) + { + return Equivalent(other); + } + + /** + * test for equivalence - note: case is ignored. + */ + public bool Equivalent( + X509Name other) + { + if (other == null) + return false; + + if (other == this) + return true; + + int orderingSize = ordering.Count; + + if (orderingSize != other.ordering.Count) + { + return false; + } + + bool[] indexes = new bool[orderingSize]; + int start, end, delta; + + if (ordering[0].Equals(other.ordering[0])) // guess forward + { + start = 0; + end = orderingSize; + delta = 1; + } + else // guess reversed - most common problem + { + start = orderingSize - 1; + end = -1; + delta = -1; + } + + for (int i = start; i != end; i += delta) + { + bool found = false; + DerObjectIdentifier oid = (DerObjectIdentifier)ordering[i]; + string value = (string)values[i]; + + for (int j = 0; j < orderingSize; j++) + { + if (indexes[j]) + { + continue; + } + + DerObjectIdentifier oOid = (DerObjectIdentifier)other.ordering[j]; + + if (oid.Equals(oOid)) + { + string oValue = (string)other.values[j]; + + if (equivalentStrings(value, oValue)) + { + indexes[j] = true; + found = true; + break; + } + } + } + + if (!found) + { + return false; + } + } + + return true; + } + + private static bool equivalentStrings( + string s1, + string s2) + { + string v1 = canonicalize(s1); + string v2 = canonicalize(s2); + + if (!v1.Equals(v2)) + { + v1 = stripInternalSpaces(v1); + v2 = stripInternalSpaces(v2); + + if (!v1.Equals(v2)) + { + return false; + } + } + + return true; + } + + private static string canonicalize( + string s) + { + string v = s.ToLower(CultureInfo.InvariantCulture).Trim(); + + if (v.StartsWith("#")) + { + Asn1Object obj = decodeObject(v); + + if (obj is IAsn1String) + { + v = ((IAsn1String)obj).GetString().ToLower(CultureInfo.InvariantCulture).Trim(); + } + } + + return v; + } + + private static Asn1Object decodeObject( + string v) + { + try + { + return Asn1Object.FromByteArray(Hex.Decode(v.Substring(1))); + } + catch (IOException e) + { + throw new InvalidOperationException("unknown encoding in name: " + e.Message, e); + } + } + + private static string stripInternalSpaces( + string str) + { + StringBuilder res = new StringBuilder(); + + if (str.Length != 0) + { + char c1 = str[0]; + + res.Append(c1); + + for (int k = 1; k < str.Length; k++) + { + char c2 = str[k]; + if (!(c1 == ' ' && c2 == ' ')) + { + res.Append(c2); + } + c1 = c2; + } + } + + return res.ToString(); + } + + private void AppendValue( + StringBuilder buf, + Hashtable oidSymbols, + DerObjectIdentifier oid, + string val) + { + string sym = (string) oidSymbols[oid]; + + if (sym != null) + { + buf.Append(sym); + } + else + { + buf.Append(oid.Id); + } + + buf.Append('='); + + int index = buf.Length; + + buf.Append(val); + + int end = buf.Length; + + if (val.StartsWith("\\#")) + { + index += 2; + } + + while (index != end) + { + if ((buf[index] == ',') + || (buf[index] == '"') + || (buf[index] == '\\') + || (buf[index] == '+') + || (buf[index] == '=') + || (buf[index] == '<') + || (buf[index] == '>') + || (buf[index] == ';')) + { + buf.Insert(index++, "\\"); + end++; + } + + index++; + } + } + + /** + * convert the structure to a string - if reverse is true the + * oids and values are listed out starting with the last element + * in the sequence (ala RFC 2253), otherwise the string will begin + * with the first element of the structure. If no string definition + * for the oid is found in oidSymbols the string value of the oid is + * added. Two standard symbol tables are provided DefaultSymbols, and + * RFC2253Symbols as part of this class. + * + * @param reverse if true start at the end of the sequence and work back. + * @param oidSymbols look up table strings for oids. + */ + public string ToString( + bool reverse, + Hashtable oidSymbols) + { + ArrayList components = new ArrayList(); + + StringBuilder ava = null; + + for (int i = 0; i < ordering.Count; i++) + { + if ((bool) added[i]) + { + ava.Append('+'); + AppendValue(ava, oidSymbols, + (DerObjectIdentifier)ordering[i], + (string)values[i]); + } + else + { + ava = new StringBuilder(); + AppendValue(ava, oidSymbols, + (DerObjectIdentifier)ordering[i], + (string)values[i]); + components.Add(ava); + } + } + + if (reverse) + { + components.Reverse(); + } + + StringBuilder buf = new StringBuilder(); + + if (components.Count > 0) + { + buf.Append(components[0].ToString()); + + for (int i = 1; i < components.Count; ++i) + { + buf.Append(','); + buf.Append(components[i].ToString()); + } + } + + return buf.ToString(); + } + + public override string ToString() + { + return ToString(DefaultReverse, DefaultSymbols); + } + } +} diff --git a/src/core/srcbc/asn1/x509/X509ObjectIdentifiers.cs b/src/core/srcbc/asn1/x509/X509ObjectIdentifiers.cs index 87da8cb..289e193 100644 --- a/src/core/srcbc/asn1/x509/X509ObjectIdentifiers.cs +++ b/src/core/srcbc/asn1/x509/X509ObjectIdentifiers.cs @@ -14,6 +14,9 @@ namespace Org.BouncyCastle.Asn1.X509 public static readonly DerObjectIdentifier Organization = new DerObjectIdentifier(ID + ".10"); public static readonly DerObjectIdentifier OrganizationalUnitName = new DerObjectIdentifier(ID + ".11"); + public static readonly DerObjectIdentifier id_at_telephoneNumber = new DerObjectIdentifier(ID + ".20"); + public static readonly DerObjectIdentifier id_at_name = new DerObjectIdentifier(ID + ".41"); + // id-SHA1 OBJECT IDENTIFIER ::= // {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 } // public static readonly DerObjectIdentifier IdSha1 = new DerObjectIdentifier("1.3.14.3.2.26"); diff --git a/src/core/srcbc/bcpg/LiteralDataPacket.cs b/src/core/srcbc/bcpg/LiteralDataPacket.cs index 2ca3f1f..dbb1521 100644 --- a/src/core/srcbc/bcpg/LiteralDataPacket.cs +++ b/src/core/srcbc/bcpg/LiteralDataPacket.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using Org.BouncyCastle.Utilities; + namespace Org.BouncyCastle.Bcpg { /// Generic literal data packet. @@ -47,5 +49,10 @@ namespace Org.BouncyCastle.Bcpg { get { return fileName; } } - } + + public byte[] GetRawFileName() + { + return Strings.ToByteArray(fileName); + } + } } diff --git a/src/core/srcbc/cms/SignerInformation.cs b/src/core/srcbc/cms/SignerInformation.cs index 440a49f..d09b1c9 100644 --- a/src/core/srcbc/cms/SignerInformation.cs +++ b/src/core/srcbc/cms/SignerInformation.cs @@ -1,637 +1,672 @@ -using System; -using System.Collections; -using System.Diagnostics; -using System.IO; - -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Cms; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities; -using Org.BouncyCastle.X509; - -namespace Org.BouncyCastle.Cms -{ - /** - * an expanded SignerInfo block from a CMS Signed message - */ - public class SignerInformation - { - private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; - - private SignerID sid; - private SignerInfo info; - private AlgorithmIdentifier digestAlgorithm; - private AlgorithmIdentifier encryptionAlgorithm; - private Asn1Set signedAttributes; - private Asn1Set unsignedAttributes; - private CmsProcessable content; - private byte[] signature; - private DerObjectIdentifier contentType; - private IDigestCalculator digestCalculator; - private byte[] resultDigest; - - internal SignerInformation( - SignerInfo info, - DerObjectIdentifier contentType, - CmsProcessable content, - IDigestCalculator digestCalculator) - { - this.info = info; - this.sid = new SignerID(); - this.contentType = contentType; - - try - { - SignerIdentifier s = info.SignerID; - - if (s.IsTagged) - { - Asn1OctetString octs = Asn1OctetString.GetInstance(s.ID); - - sid.SubjectKeyIdentifier = octs.GetEncoded(); - } - else - { - Asn1.Cms.IssuerAndSerialNumber iAnds = - Asn1.Cms.IssuerAndSerialNumber.GetInstance(s.ID); - - sid.Issuer = iAnds.Name; - sid.SerialNumber = iAnds.SerialNumber.Value; - } - } - catch (IOException) - { - throw new ArgumentException("invalid sid in SignerInfo"); - } - - this.digestAlgorithm = info.DigestAlgorithm; - this.signedAttributes = info.AuthenticatedAttributes; - this.unsignedAttributes = info.UnauthenticatedAttributes; - this.encryptionAlgorithm = info.DigestEncryptionAlgorithm; - this.signature = info.EncryptedDigest.GetOctets(); - - this.content = content; - this.digestCalculator = digestCalculator; - } - - public SignerID SignerID - { - get { return sid; } - } - - /** - * return the version number for this objects underlying SignerInfo structure. - */ - public int Version - { - get { return info.Version.Value.IntValue; } - } - - public AlgorithmIdentifier DigestAlgorithmID - { - get { return digestAlgorithm; } - } - - /** - * return the object identifier for the signature. - */ - public string DigestAlgOid - { - get { return digestAlgorithm.ObjectID.Id; } - } - - /** - * return the signature parameters, or null if there aren't any. - */ - public Asn1Object DigestAlgParams - { - get - { - Asn1Encodable ae = digestAlgorithm.Parameters; - - return ae == null ? null : ae.ToAsn1Object(); - } - } - - /** - * return the content digest that was calculated during verification. - */ - public byte[] GetContentDigest() - { - if (resultDigest == null) - { - throw new InvalidOperationException("method can only be called after verify."); - } - - return (byte[])resultDigest.Clone(); - } - - public AlgorithmIdentifier EncryptionAlgorithmID - { - get { return encryptionAlgorithm; } - } - - /** - * return the object identifier for the signature. - */ - public string EncryptionAlgOid - { - get { return encryptionAlgorithm.ObjectID.Id; } - } - - /** - * return the signature/encryption algorithm parameters, or null if - * there aren't any. - */ - public Asn1Object EncryptionAlgParams - { - get - { - Asn1Encodable ae = encryptionAlgorithm.Parameters; - - return ae == null ? null : ae.ToAsn1Object(); - } - } - - /** - * return a table of the signed attributes - indexed by - * the OID of the attribute. - */ - public Asn1.Cms.AttributeTable SignedAttributes - { - get - { - return signedAttributes == null - ? null - : new Asn1.Cms.AttributeTable(signedAttributes); - } - } - - /** - * return a table of the unsigned attributes indexed by - * the OID of the attribute. - */ - public Asn1.Cms.AttributeTable UnsignedAttributes - { - get - { - return unsignedAttributes == null - ? null - : new Asn1.Cms.AttributeTable(unsignedAttributes); - } - } - - /** - * return the encoded signature - */ - public byte[] GetSignature() - { - return (byte[]) signature.Clone(); - } - - /** - * Return a SignerInformationStore containing the counter signatures attached to this - * signer. If no counter signatures are present an empty store is returned. - */ - public SignerInformationStore GetCounterSignatures() - { - Asn1.Cms.AttributeTable unsignedAttributeTable = UnsignedAttributes; - if (unsignedAttributeTable == null) - { - return new SignerInformationStore(new ArrayList(0)); - } - - IList counterSignatures = new ArrayList(); - - Asn1.Cms.Attribute counterSignatureAttribute = unsignedAttributeTable[CmsAttributes.CounterSignature]; - if (counterSignatureAttribute != null) - { - Asn1Set values = counterSignatureAttribute.AttrValues; - counterSignatures = new ArrayList(values.Count); - - foreach (Asn1Encodable asn1Obj in values) - { - SignerInfo si = SignerInfo.GetInstance(asn1Obj.ToAsn1Object()); - - string digestName = CmsSignedHelper.Instance.GetDigestAlgName(si.DigestAlgorithm.ObjectID.Id); - - counterSignatures.Add(new SignerInformation(si, CmsAttributes.CounterSignature, null, new CounterSignatureDigestCalculator(digestName, GetSignature()))); - } - } - - return new SignerInformationStore(counterSignatures); - } - - /** - * return the DER encoding of the signed attributes. - * @throws IOException if an encoding error occurs. - */ - public byte[] GetEncodedSignedAttributes() - { - return signedAttributes == null - ? null - : signedAttributes.GetEncoded(Asn1Encodable.Der); - } - - private bool DoVerify( - AsymmetricKeyParameter key, - Asn1.Cms.AttributeTable signedAttrTable) - { - string digestName = Helper.GetDigestAlgName(this.DigestAlgOid); - IDigest digest = Helper.GetDigestInstance(digestName); - - DerObjectIdentifier sigAlgOid = this.encryptionAlgorithm.ObjectID; - Asn1Encodable sigParams = this.encryptionAlgorithm.Parameters; - ISigner sig; - - if (sigAlgOid.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdRsassaPss)) - { - // RFC 4056 2.2 - // When the id-RSASSA-PSS algorithm identifier is used for a signature, - // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params. - if (sigParams == null) - throw new CmsException("RSASSA-PSS signature must specify algorithm parameters"); - - try - { - // TODO Provide abstract configuration mechanism - - Asn1.Pkcs.RsassaPssParameters pss = Asn1.Pkcs.RsassaPssParameters.GetInstance( - sigParams.ToAsn1Object()); - - if (!pss.HashAlgorithm.ObjectID.Equals(this.digestAlgorithm.ObjectID)) - throw new CmsException("RSASSA-PSS signature parameters specified incorrect hash algorithm"); - if (!pss.MaskGenAlgorithm.ObjectID.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdMgf1)) - throw new CmsException("RSASSA-PSS signature parameters specified unknown MGF"); - - IDigest pssDigest = DigestUtilities.GetDigest(pss.HashAlgorithm.ObjectID); - int saltLength = pss.SaltLength.Value.IntValue; - byte trailerField = (byte) pss.TrailerField.Value.IntValue; - - // RFC 4055 3.1 - // The value MUST be 1, which represents the trailer field with hexadecimal value 0xBC - if (trailerField != 1) - throw new CmsException("RSASSA-PSS signature parameters must have trailerField of 1"); - - sig = new PssSigner(new RsaBlindedEngine(), pssDigest, saltLength); - } - catch (Exception e) - { - throw new CmsException("failed to set RSASSA-PSS signature parameters", e); - } - } - else - { - // TODO Probably too strong a check at the moment -// if (sigParams != null) -// throw new CmsException("unrecognised signature parameters provided"); - - string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(this.EncryptionAlgOid); - - sig = Helper.GetSignatureInstance(signatureName); - } - - try - { - sig.Init(false, key); - - if (signedAttributes == null) - { - if (content != null) - { - content.Write(new CmsSignedDataGenerator.SigOutputStream(sig)); - content.Write(new CmsSignedDataGenerator.DigOutputStream(digest)); - - resultDigest = DigestUtilities.DoFinal(digest); - } - else - { - resultDigest = digestCalculator.GetDigest(); - - // need to decrypt signature and check message bytes - return VerifyDigest(resultDigest, key, this.GetSignature()); - } - } - else - { - byte[] hash; - if (content != null) - { - content.Write( - new CmsSignedDataGenerator.DigOutputStream(digest)); - - hash = DigestUtilities.DoFinal(digest); - } - else if (digestCalculator != null) - { - hash = digestCalculator.GetDigest(); - } - else - { - hash = null; - } - - resultDigest = hash; - - Asn1.Cms.Attribute dig = signedAttrTable[Asn1.Cms.CmsAttributes.MessageDigest]; - Asn1.Cms.Attribute type = signedAttrTable[Asn1.Cms.CmsAttributes.ContentType]; - - if (dig == null) - { - throw new SignatureException("no hash for content found in signed attributes"); - } - - if (type == null && !contentType.Equals(CmsAttributes.CounterSignature)) - { - throw new SignatureException("no content type id found in signed attributes"); - } - - Asn1Object hashObj = dig.AttrValues[0].ToAsn1Object(); - - if (hashObj is Asn1OctetString) - { - byte[] signedHash = ((Asn1OctetString)hashObj).GetOctets(); - - if (!Arrays.AreEqual(hash, signedHash)) - { - throw new SignatureException("content hash found in signed attributes different"); - } - } - else if (hashObj is DerNull) - { - if (hash != null) - { - throw new SignatureException("NULL hash found in signed attributes when one expected"); - } - } - - if (type != null) - { - DerObjectIdentifier typeOID = (DerObjectIdentifier)type.AttrValues[0]; - - if (!typeOID.Equals(contentType)) - { - throw new SignatureException("contentType in signed attributes different"); - } - } - - byte[] tmp = this.GetEncodedSignedAttributes(); - sig.BlockUpdate(tmp, 0, tmp.Length); - } - - return sig.VerifySignature(this.GetSignature()); - } - catch (InvalidKeyException e) - { - throw new CmsException( - "key not appropriate to signature in message.", e); - } - catch (IOException e) - { - throw new CmsException( - "can't process mime object to create signature.", e); - } - catch (SignatureException e) - { - throw new CmsException( - "invalid signature format in message: " + e.Message, e); - } - } - - private bool IsNull( - Asn1Encodable o) - { - return (o is Asn1Null) || (o == null); - } - - private DigestInfo DerDecode( - byte[] encoding) - { - if (encoding[0] != (int)(Asn1Tags.Constructed | Asn1Tags.Sequence)) - { - throw new IOException("not a digest info object"); - } - - DigestInfo digInfo = DigestInfo.GetInstance(Asn1Object.FromByteArray(encoding)); - - // length check to avoid Bleichenbacher vulnerability - - if (digInfo.GetEncoded().Length != encoding.Length) - { - throw new CmsException("malformed RSA signature"); - } - - return digInfo; - } - - private bool VerifyDigest( - byte[] digest, - AsymmetricKeyParameter key, - byte[] signature) - { - string algorithm = Helper.GetEncryptionAlgName(this.EncryptionAlgOid); - - try - { - if (algorithm.Equals("RSA")) - { - IBufferedCipher c = CipherUtilities.GetCipher("RSA//PKCS1Padding"); - - c.Init(false, key); - - byte[] decrypt = c.DoFinal(signature); - - DigestInfo digInfo = DerDecode(decrypt); - - if (!digInfo.AlgorithmID.ObjectID.Equals(digestAlgorithm.ObjectID)) - { - return false; - } - - if (!IsNull(digInfo.AlgorithmID.Parameters)) - { - return false; - } - - byte[] sigHash = digInfo.GetDigest(); - - return Arrays.AreEqual(digest, sigHash); - } - else if (algorithm.Equals("DSA")) - { - ISigner sig = SignerUtilities.GetSigner("NONEwithDSA"); - - sig.Init(false, key); - - sig.BlockUpdate(digest, 0, digest.Length); - - return sig.VerifySignature(signature); - } - else - { - throw new CmsException("algorithm: " + algorithm + " not supported in base signatures."); - } - } - catch (SecurityUtilityException e) - { - throw e; - } - catch (GeneralSecurityException e) - { - throw new CmsException("Exception processing signature: " + e, e); - } - catch (IOException e) - { - throw new CmsException("Exception decoding signature: " + e, e); - } - } - - /** - * verify that the given public key succesfully handles and confirms the - * signature associated with this signer. - */ - public bool Verify( - AsymmetricKeyParameter pubKey) - { - if (pubKey.IsPrivate) - throw new ArgumentException("Expected public key", "pubKey"); - - return DoVerify(pubKey, this.SignedAttributes); - } - - /** - * verify that the given certificate successfully handles and confirms - * the signature associated with this signer and, if a signingTime - * attribute is available, that the certificate was valid at the time the - * signature was generated. - */ - public bool Verify( - X509Certificate cert) - { - Asn1.Cms.AttributeTable attr = this.SignedAttributes; - - if (attr != null) - { - Asn1EncodableVector v = attr.GetAll(CmsAttributes.SigningTime); - switch (v.Count) - { - case 0: - break; - case 1: - { - Asn1.Cms.Attribute t = (Asn1.Cms.Attribute) v[0]; - Debug.Assert(t != null); - - Asn1Set attrValues = t.AttrValues; - if (attrValues.Count != 1) - throw new CmsException("A signing-time attribute MUST have a single attribute value"); - - Asn1.Cms.Time time = Asn1.Cms.Time.GetInstance(attrValues[0].ToAsn1Object()); - - cert.CheckValidity(time.Date); - break; - } - default: - throw new CmsException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the signing-time attribute"); - } - } - - return DoVerify(cert.GetPublicKey(), attr); - } - - /** - * Return the base ASN.1 CMS structure that this object contains. - * - * @return an object containing a CMS SignerInfo structure. - */ - public SignerInfo ToSignerInfo() - { - return info; - } - - /** - * Return a signer information object with the passed in unsigned - * attributes replacing the ones that are current associated with - * the object passed in. - * - * @param signerInformation the signerInfo to be used as the basis. - * @param unsignedAttributes the unsigned attributes to add. - * @return a copy of the original SignerInformationObject with the changed attributes. - */ - public static SignerInformation ReplaceUnsignedAttributes( - SignerInformation signerInformation, - Asn1.Cms.AttributeTable unsignedAttributes) - { - SignerInfo sInfo = signerInformation.info; - Asn1Set unsignedAttr = null; - - if (unsignedAttributes != null) - { - unsignedAttr = new DerSet(unsignedAttributes.ToAsn1EncodableVector()); - } - - return new SignerInformation( - new SignerInfo( - sInfo.SignerID, - sInfo.DigestAlgorithm, - sInfo.AuthenticatedAttributes, - sInfo.DigestEncryptionAlgorithm, - sInfo.EncryptedDigest, - unsignedAttr), - signerInformation.contentType, - signerInformation.content, - null); - } - - /** - * Return a signer information object with passed in SignerInformationStore representing counter - * signatures attached as an unsigned attribute. - * - * @param signerInformation the signerInfo to be used as the basis. - * @param counterSigners signer info objects carrying counter signature. - * @return a copy of the original SignerInformationObject with the changed attributes. - */ - public static SignerInformation AddCounterSigners( - SignerInformation signerInformation, - SignerInformationStore counterSigners) - { - SignerInfo sInfo = signerInformation.info; - Asn1.Cms.AttributeTable unsignedAttr = signerInformation.UnsignedAttributes; - Asn1EncodableVector v; - - if (unsignedAttr != null) - { - v = unsignedAttr.ToAsn1EncodableVector(); - } - else - { - v = new Asn1EncodableVector(); - } - - Asn1EncodableVector sigs = new Asn1EncodableVector(); - - foreach (SignerInformation sigInf in counterSigners.GetSigners()) - { - sigs.Add(sigInf.ToSignerInfo()); - } - - v.Add(new Asn1.Cms.Attribute(CmsAttributes.CounterSignature, new DerSet(sigs))); - - return new SignerInformation( - new SignerInfo( - sInfo.SignerID, - sInfo.DigestAlgorithm, - sInfo.AuthenticatedAttributes, - sInfo.DigestEncryptionAlgorithm, - sInfo.EncryptedDigest, - new DerSet(v)), - signerInformation.contentType, - signerInformation.content, - null); - } - } -} +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Cms +{ + /** + * an expanded SignerInfo block from a CMS Signed message + */ + public class SignerInformation + { + private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance; + + private SignerID sid; + private SignerInfo info; + private AlgorithmIdentifier digestAlgorithm; + private AlgorithmIdentifier encryptionAlgorithm; + private Asn1Set signedAttributes; + private Asn1Set unsignedAttributes; + private CmsProcessable content; + private byte[] signature; + private DerObjectIdentifier contentType; + private IDigestCalculator digestCalculator; + private byte[] resultDigest; + + internal SignerInformation( + SignerInfo info, + DerObjectIdentifier contentType, + CmsProcessable content, + IDigestCalculator digestCalculator) + { + this.info = info; + this.sid = new SignerID(); + this.contentType = contentType; + + try + { + SignerIdentifier s = info.SignerID; + + if (s.IsTagged) + { + Asn1OctetString octs = Asn1OctetString.GetInstance(s.ID); + + sid.SubjectKeyIdentifier = octs.GetEncoded(); + } + else + { + Asn1.Cms.IssuerAndSerialNumber iAnds = + Asn1.Cms.IssuerAndSerialNumber.GetInstance(s.ID); + + sid.Issuer = iAnds.Name; + sid.SerialNumber = iAnds.SerialNumber.Value; + } + } + catch (IOException) + { + throw new ArgumentException("invalid sid in SignerInfo"); + } + + this.digestAlgorithm = info.DigestAlgorithm; + this.signedAttributes = info.AuthenticatedAttributes; + this.unsignedAttributes = info.UnauthenticatedAttributes; + this.encryptionAlgorithm = info.DigestEncryptionAlgorithm; + this.signature = info.EncryptedDigest.GetOctets(); + + this.content = content; + this.digestCalculator = digestCalculator; + } + + public SignerID SignerID + { + get { return sid; } + } + + /** + * return the version number for this objects underlying SignerInfo structure. + */ + public int Version + { + get { return info.Version.Value.IntValue; } + } + + public AlgorithmIdentifier DigestAlgorithmID + { + get { return digestAlgorithm; } + } + + /** + * return the object identifier for the signature. + */ + public string DigestAlgOid + { + get { return digestAlgorithm.ObjectID.Id; } + } + + /** + * return the signature parameters, or null if there aren't any. + */ + public Asn1Object DigestAlgParams + { + get + { + Asn1Encodable ae = digestAlgorithm.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + /** + * return the content digest that was calculated during verification. + */ + public byte[] GetContentDigest() + { + if (resultDigest == null) + { + throw new InvalidOperationException("method can only be called after verify."); + } + + return (byte[])resultDigest.Clone(); + } + + public AlgorithmIdentifier EncryptionAlgorithmID + { + get { return encryptionAlgorithm; } + } + + /** + * return the object identifier for the signature. + */ + public string EncryptionAlgOid + { + get { return encryptionAlgorithm.ObjectID.Id; } + } + + /** + * return the signature/encryption algorithm parameters, or null if + * there aren't any. + */ + public Asn1Object EncryptionAlgParams + { + get + { + Asn1Encodable ae = encryptionAlgorithm.Parameters; + + return ae == null ? null : ae.ToAsn1Object(); + } + } + + /** + * return a table of the signed attributes - indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable SignedAttributes + { + get + { + return signedAttributes == null + ? null + : new Asn1.Cms.AttributeTable(signedAttributes); + } + } + + /** + * return a table of the unsigned attributes indexed by + * the OID of the attribute. + */ + public Asn1.Cms.AttributeTable UnsignedAttributes + { + get + { + return unsignedAttributes == null + ? null + : new Asn1.Cms.AttributeTable(unsignedAttributes); + } + } + + /** + * return the encoded signature + */ + public byte[] GetSignature() + { + return (byte[]) signature.Clone(); + } + + /** + * Return a SignerInformationStore containing the counter signatures attached to this + * signer. If no counter signatures are present an empty store is returned. + */ + public SignerInformationStore GetCounterSignatures() + { + // TODO There are several checks implied by the RFC3852 comments that are missing + + /* + The countersignature attribute MUST be an unsigned attribute; it MUST + NOT be a signed attribute, an authenticated attribute, an + unauthenticated attribute, or an unprotected attribute. + */ + Asn1.Cms.AttributeTable unsignedAttributeTable = UnsignedAttributes; + if (unsignedAttributeTable == null) + { + return new SignerInformationStore(new ArrayList(0)); + } + + IList counterSignatures = new ArrayList(); + + /* + The UnsignedAttributes syntax is defined as a SET OF Attributes. The + UnsignedAttributes in a signerInfo may include multiple instances of + the countersignature attribute. + */ + Asn1EncodableVector allCSAttrs = unsignedAttributeTable.GetAll(CmsAttributes.CounterSignature); + + foreach (Asn1.Cms.Attribute counterSignatureAttribute in allCSAttrs) + { + /* + A countersignature attribute can have multiple attribute values. The + syntax is defined as a SET OF AttributeValue, and there MUST be one + or more instances of AttributeValue present. + */ + Asn1Set values = counterSignatureAttribute.AttrValues; + if (values.Count < 1) + { + // TODO Throw an appropriate exception? + } + + foreach (Asn1Encodable asn1Obj in values) + { + /* + Countersignature values have the same meaning as SignerInfo values + for ordinary signatures, except that: + + 1. The signedAttributes field MUST NOT contain a content-type + attribute; there is no content type for countersignatures. + + 2. The signedAttributes field MUST contain a message-digest + attribute if it contains any other attributes. + + 3. The input to the message-digesting process is the contents + octets of the DER encoding of the signatureValue field of the + SignerInfo value with which the attribute is associated. + */ + SignerInfo si = SignerInfo.GetInstance(asn1Obj.ToAsn1Object()); + + string digestName = CmsSignedHelper.Instance.GetDigestAlgName(si.DigestAlgorithm.ObjectID.Id); + + counterSignatures.Add(new SignerInformation(si, CmsAttributes.CounterSignature, null, new CounterSignatureDigestCalculator(digestName, GetSignature()))); + } + } + + return new SignerInformationStore(counterSignatures); + } + + /** + * return the DER encoding of the signed attributes. + * @throws IOException if an encoding error occurs. + */ + public byte[] GetEncodedSignedAttributes() + { + return signedAttributes == null + ? null + : signedAttributes.GetEncoded(Asn1Encodable.Der); + } + + private bool DoVerify( + AsymmetricKeyParameter key, + Asn1.Cms.AttributeTable signedAttrTable) + { + string digestName = Helper.GetDigestAlgName(this.DigestAlgOid); + IDigest digest = Helper.GetDigestInstance(digestName); + + DerObjectIdentifier sigAlgOid = this.encryptionAlgorithm.ObjectID; + Asn1Encodable sigParams = this.encryptionAlgorithm.Parameters; + ISigner sig; + + if (sigAlgOid.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdRsassaPss)) + { + // RFC 4056 2.2 + // When the id-RSASSA-PSS algorithm identifier is used for a signature, + // the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params. + if (sigParams == null) + throw new CmsException("RSASSA-PSS signature must specify algorithm parameters"); + + try + { + // TODO Provide abstract configuration mechanism + + Asn1.Pkcs.RsassaPssParameters pss = Asn1.Pkcs.RsassaPssParameters.GetInstance( + sigParams.ToAsn1Object()); + + if (!pss.HashAlgorithm.ObjectID.Equals(this.digestAlgorithm.ObjectID)) + throw new CmsException("RSASSA-PSS signature parameters specified incorrect hash algorithm"); + if (!pss.MaskGenAlgorithm.ObjectID.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdMgf1)) + throw new CmsException("RSASSA-PSS signature parameters specified unknown MGF"); + + IDigest pssDigest = DigestUtilities.GetDigest(pss.HashAlgorithm.ObjectID); + int saltLength = pss.SaltLength.Value.IntValue; + byte trailerField = (byte) pss.TrailerField.Value.IntValue; + + // RFC 4055 3.1 + // The value MUST be 1, which represents the trailer field with hexadecimal value 0xBC + if (trailerField != 1) + throw new CmsException("RSASSA-PSS signature parameters must have trailerField of 1"); + + sig = new PssSigner(new RsaBlindedEngine(), pssDigest, saltLength); + } + catch (Exception e) + { + throw new CmsException("failed to set RSASSA-PSS signature parameters", e); + } + } + else + { + // TODO Probably too strong a check at the moment +// if (sigParams != null) +// throw new CmsException("unrecognised signature parameters provided"); + + string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(this.EncryptionAlgOid); + + sig = Helper.GetSignatureInstance(signatureName); + } + + try + { + sig.Init(false, key); + + if (signedAttributes == null) + { + if (content != null) + { + content.Write(new CmsSignedDataGenerator.SigOutputStream(sig)); + content.Write(new CmsSignedDataGenerator.DigOutputStream(digest)); + + resultDigest = DigestUtilities.DoFinal(digest); + } + else + { + resultDigest = digestCalculator.GetDigest(); + + // need to decrypt signature and check message bytes + return VerifyDigest(resultDigest, key, this.GetSignature()); + } + } + else + { + byte[] hash; + if (content != null) + { + content.Write( + new CmsSignedDataGenerator.DigOutputStream(digest)); + + hash = DigestUtilities.DoFinal(digest); + } + else if (digestCalculator != null) + { + hash = digestCalculator.GetDigest(); + } + else + { + hash = null; + } + + resultDigest = hash; + + Asn1.Cms.Attribute dig = signedAttrTable[Asn1.Cms.CmsAttributes.MessageDigest]; + Asn1.Cms.Attribute type = signedAttrTable[Asn1.Cms.CmsAttributes.ContentType]; + + if (dig == null) + { + throw new SignatureException("no hash for content found in signed attributes"); + } + + if (type == null && !contentType.Equals(CmsAttributes.CounterSignature)) + { + throw new SignatureException("no content type id found in signed attributes"); + } + + Asn1Object hashObj = dig.AttrValues[0].ToAsn1Object(); + + if (hashObj is Asn1OctetString) + { + byte[] signedHash = ((Asn1OctetString)hashObj).GetOctets(); + + if (!Arrays.AreEqual(hash, signedHash)) + { + throw new SignatureException("content hash found in signed attributes different"); + } + } + else if (hashObj is DerNull) + { + if (hash != null) + { + throw new SignatureException("NULL hash found in signed attributes when one expected"); + } + } + + if (type != null) + { + DerObjectIdentifier typeOID = (DerObjectIdentifier)type.AttrValues[0]; + + if (!typeOID.Equals(contentType)) + { + throw new SignatureException("contentType in signed attributes different"); + } + } + + byte[] tmp = this.GetEncodedSignedAttributes(); + sig.BlockUpdate(tmp, 0, tmp.Length); + } + + return sig.VerifySignature(this.GetSignature()); + } + catch (InvalidKeyException e) + { + throw new CmsException( + "key not appropriate to signature in message.", e); + } + catch (IOException e) + { + throw new CmsException( + "can't process mime object to create signature.", e); + } + catch (SignatureException e) + { + throw new CmsException( + "invalid signature format in message: " + e.Message, e); + } + } + + private bool IsNull( + Asn1Encodable o) + { + return (o is Asn1Null) || (o == null); + } + + private DigestInfo DerDecode( + byte[] encoding) + { + if (encoding[0] != (int)(Asn1Tags.Constructed | Asn1Tags.Sequence)) + { + throw new IOException("not a digest info object"); + } + + DigestInfo digInfo = DigestInfo.GetInstance(Asn1Object.FromByteArray(encoding)); + + // length check to avoid Bleichenbacher vulnerability + + if (digInfo.GetEncoded().Length != encoding.Length) + { + throw new CmsException("malformed RSA signature"); + } + + return digInfo; + } + + private bool VerifyDigest( + byte[] digest, + AsymmetricKeyParameter key, + byte[] signature) + { + string algorithm = Helper.GetEncryptionAlgName(this.EncryptionAlgOid); + + try + { + if (algorithm.Equals("RSA")) + { + IBufferedCipher c = CipherUtilities.GetCipher("RSA//PKCS1Padding"); + + c.Init(false, key); + + byte[] decrypt = c.DoFinal(signature); + + DigestInfo digInfo = DerDecode(decrypt); + + if (!digInfo.AlgorithmID.ObjectID.Equals(digestAlgorithm.ObjectID)) + { + return false; + } + + if (!IsNull(digInfo.AlgorithmID.Parameters)) + { + return false; + } + + byte[] sigHash = digInfo.GetDigest(); + + return Arrays.AreEqual(digest, sigHash); + } + else if (algorithm.Equals("DSA")) + { + ISigner sig = SignerUtilities.GetSigner("NONEwithDSA"); + + sig.Init(false, key); + + sig.BlockUpdate(digest, 0, digest.Length); + + return sig.VerifySignature(signature); + } + else + { + throw new CmsException("algorithm: " + algorithm + " not supported in base signatures."); + } + } + catch (SecurityUtilityException e) + { + throw e; + } + catch (GeneralSecurityException e) + { + throw new CmsException("Exception processing signature: " + e, e); + } + catch (IOException e) + { + throw new CmsException("Exception decoding signature: " + e, e); + } + } + + /** + * verify that the given public key succesfully handles and confirms the + * signature associated with this signer. + */ + public bool Verify( + AsymmetricKeyParameter pubKey) + { + if (pubKey.IsPrivate) + throw new ArgumentException("Expected public key", "pubKey"); + + return DoVerify(pubKey, this.SignedAttributes); + } + + /** + * verify that the given certificate successfully handles and confirms + * the signature associated with this signer and, if a signingTime + * attribute is available, that the certificate was valid at the time the + * signature was generated. + */ + public bool Verify( + X509Certificate cert) + { + Asn1.Cms.AttributeTable attr = this.SignedAttributes; + + if (attr != null) + { + Asn1EncodableVector v = attr.GetAll(CmsAttributes.SigningTime); + switch (v.Count) + { + case 0: + break; + case 1: + { + Asn1.Cms.Attribute t = (Asn1.Cms.Attribute) v[0]; + Debug.Assert(t != null); + + Asn1Set attrValues = t.AttrValues; + if (attrValues.Count != 1) + throw new CmsException("A signing-time attribute MUST have a single attribute value"); + + Asn1.Cms.Time time = Asn1.Cms.Time.GetInstance(attrValues[0].ToAsn1Object()); + + cert.CheckValidity(time.Date); + break; + } + default: + throw new CmsException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the signing-time attribute"); + } + } + + return DoVerify(cert.GetPublicKey(), attr); + } + + /** + * Return the base ASN.1 CMS structure that this object contains. + * + * @return an object containing a CMS SignerInfo structure. + */ + public SignerInfo ToSignerInfo() + { + return info; + } + + /** + * Return a signer information object with the passed in unsigned + * attributes replacing the ones that are current associated with + * the object passed in. + * + * @param signerInformation the signerInfo to be used as the basis. + * @param unsignedAttributes the unsigned attributes to add. + * @return a copy of the original SignerInformationObject with the changed attributes. + */ + public static SignerInformation ReplaceUnsignedAttributes( + SignerInformation signerInformation, + Asn1.Cms.AttributeTable unsignedAttributes) + { + SignerInfo sInfo = signerInformation.info; + Asn1Set unsignedAttr = null; + + if (unsignedAttributes != null) + { + unsignedAttr = new DerSet(unsignedAttributes.ToAsn1EncodableVector()); + } + + return new SignerInformation( + new SignerInfo( + sInfo.SignerID, + sInfo.DigestAlgorithm, + sInfo.AuthenticatedAttributes, + sInfo.DigestEncryptionAlgorithm, + sInfo.EncryptedDigest, + unsignedAttr), + signerInformation.contentType, + signerInformation.content, + null); + } + + /** + * Return a signer information object with passed in SignerInformationStore representing counter + * signatures attached as an unsigned attribute. + * + * @param signerInformation the signerInfo to be used as the basis. + * @param counterSigners signer info objects carrying counter signature. + * @return a copy of the original SignerInformationObject with the changed attributes. + */ + public static SignerInformation AddCounterSigners( + SignerInformation signerInformation, + SignerInformationStore counterSigners) + { + SignerInfo sInfo = signerInformation.info; + Asn1.Cms.AttributeTable unsignedAttr = signerInformation.UnsignedAttributes; + Asn1EncodableVector v; + + if (unsignedAttr != null) + { + v = unsignedAttr.ToAsn1EncodableVector(); + } + else + { + v = new Asn1EncodableVector(); + } + + Asn1EncodableVector sigs = new Asn1EncodableVector(); + + foreach (SignerInformation sigInf in counterSigners.GetSigners()) + { + sigs.Add(sigInf.ToSignerInfo()); + } + + v.Add(new Asn1.Cms.Attribute(CmsAttributes.CounterSignature, new DerSet(sigs))); + + return new SignerInformation( + new SignerInfo( + sInfo.SignerID, + sInfo.DigestAlgorithm, + sInfo.AuthenticatedAttributes, + sInfo.DigestEncryptionAlgorithm, + sInfo.EncryptedDigest, + new DerSet(v)), + signerInformation.contentType, + signerInformation.content, + null); + } + } +} diff --git a/src/core/srcbc/crypto/CryptoException.cs b/src/core/srcbc/crypto/CryptoException.cs index 644bbb9..914fb03 100644 --- a/src/core/srcbc/crypto/CryptoException.cs +++ b/src/core/srcbc/crypto/CryptoException.cs @@ -2,20 +2,20 @@ using System; namespace Org.BouncyCastle.Crypto { - public abstract class CryptoException + public class CryptoException : Exception { - protected CryptoException() + public CryptoException() { } - protected CryptoException( + public CryptoException( string message) : base(message) { } - protected CryptoException( + public CryptoException( string message, Exception exception) : base(message, exception) diff --git a/src/core/srcbc/crypto/agreement/DHAgreement.cs b/src/core/srcbc/crypto/agreement/DHAgreement.cs index 0812c6c..58e5ff4 100644 --- a/src/core/srcbc/crypto/agreement/DHAgreement.cs +++ b/src/core/srcbc/crypto/agreement/DHAgreement.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; +using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; @@ -56,12 +57,13 @@ namespace Org.BouncyCastle.Crypto.Agreement */ public BigInteger CalculateMessage() { - int bits = dhParams.P.BitLength - 1; + DHKeyPairGenerator dhGen = new DHKeyPairGenerator(); + dhGen.Init(new DHKeyGenerationParameters(random, dhParams)); + AsymmetricCipherKeyPair dhPair = dhGen.GenerateKeyPair(); - // TODO Should the generated numbers always have length 'P.BitLength - 1'? - this.privateValue = new BigInteger(bits, random).SetBit(bits - 1); + this.privateValue = ((DHPrivateKeyParameters)dhPair.Private).X; - return dhParams.G.ModPow(privateValue, dhParams.P); + return ((DHPublicKeyParameters)dhPair.Public).Y; } /** @@ -83,7 +85,9 @@ namespace Org.BouncyCastle.Crypto.Agreement throw new ArgumentException("Diffie-Hellman public key has wrong parameters."); } - return message.ModPow(key.X, dhParams.P).Multiply(pub.Y.ModPow(privateValue, dhParams.P)).Mod(dhParams.P); + BigInteger p = dhParams.P; + + return message.ModPow(key.X, p).Multiply(pub.Y.ModPow(privateValue, p)).Mod(p); } } } diff --git a/src/core/srcbc/crypto/agreement/srp/SRP6Client.cs b/src/core/srcbc/crypto/agreement/srp/SRP6Client.cs new file mode 100644 index 0000000..fcfea60 --- /dev/null +++ b/src/core/srcbc/crypto/agreement/srp/SRP6Client.cs @@ -0,0 +1,93 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + /** + * Implements the client side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + * This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + * "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + */ + public class Srp6Client + { + protected BigInteger N; + protected BigInteger g; + + protected BigInteger privA; + protected BigInteger pubA; + + protected BigInteger B; + + protected BigInteger x; + protected BigInteger u; + protected BigInteger S; + + protected IDigest digest; + protected SecureRandom random; + + public Srp6Client() + { + } + + /** + * Initialises the client to begin new authentication attempt + * @param N The safe prime associated with the client's verifier + * @param g The group parameter associated with the client's verifier + * @param digest The digest algorithm associated with the client's verifier + * @param random For key generation + */ + public virtual void Init(BigInteger N, BigInteger g, IDigest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.digest = digest; + this.random = random; + } + + /** + * Generates client's credentials given the client's salt, identity and password + * @param salt The salt used in the client's verifier. + * @param identity The user's identity (eg. username) + * @param password The user's password + * @return Client's public value to send to server + */ + public virtual BigInteger GenerateClientCredentials(byte[] salt, byte[] identity, byte[] password) + { + this.x = Srp6Utilities.CalculateX(digest, N, salt, identity, password); + this.privA = SelectPrivateValue(); + this.pubA = g.ModPow(privA, N); + + return pubA; + } + + /** + * Generates client's verification message given the server's credentials + * @param serverB The server's credentials + * @return Client's verification message for the server + * @throws CryptoException If server's credentials are invalid + */ + public virtual BigInteger CalculateSecret(BigInteger serverB) + { + this.B = Srp6Utilities.ValidatePublicValue(N, serverB); + this.u = Srp6Utilities.CalculateU(digest, N, pubA, B); + this.S = CalculateS(); + + return S; + } + + protected virtual BigInteger SelectPrivateValue() + { + return Srp6Utilities.GeneratePrivateValue(digest, N, g, random); + } + + private BigInteger CalculateS() + { + BigInteger k = Srp6Utilities.CalculateK(digest, N, g); + BigInteger exp = u.Multiply(x).Mod(N).Add(privA).Mod(N); + BigInteger tmp = g.ModPow(x, N).Multiply(k).Mod(N); + return B.Subtract(tmp).Mod(N).ModPow(exp, N); + } + } +} diff --git a/src/core/srcbc/crypto/agreement/srp/SRP6Server.cs b/src/core/srcbc/crypto/agreement/srp/SRP6Server.cs new file mode 100644 index 0000000..3494183 --- /dev/null +++ b/src/core/srcbc/crypto/agreement/srp/SRP6Server.cs @@ -0,0 +1,90 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + /** + * Implements the server side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + * This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + * "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + */ + public class Srp6Server + { + protected BigInteger N; + protected BigInteger g; + protected BigInteger v; + + protected SecureRandom random; + protected IDigest digest; + + protected BigInteger A; + + protected BigInteger privB; + protected BigInteger pubB; + + protected BigInteger u; + protected BigInteger S; + + public Srp6Server() + { + } + + /** + * Initialises the server to accept a new client authentication attempt + * @param N The safe prime associated with the client's verifier + * @param g The group parameter associated with the client's verifier + * @param v The client's verifier + * @param digest The digest algorithm associated with the client's verifier + * @param random For key generation + */ + public virtual void Init(BigInteger N, BigInteger g, BigInteger v, IDigest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.v = v; + + this.random = random; + this.digest = digest; + } + + /** + * Generates the server's credentials that are to be sent to the client. + * @return The server's public value to the client + */ + public virtual BigInteger GenerateServerCredentials() + { + BigInteger k = Srp6Utilities.CalculateK(digest, N, g); + this.privB = SelectPrivateValue(); + this.pubB = k.Multiply(v).Mod(N).Add(g.ModPow(privB, N)).Mod(N); + + return pubB; + } + + /** + * Processes the client's credentials. If valid the shared secret is generated and returned. + * @param clientA The client's credentials + * @return A shared secret BigInteger + * @throws CryptoException If client's credentials are invalid + */ + public virtual BigInteger CalculateSecret(BigInteger clientA) + { + this.A = Srp6Utilities.ValidatePublicValue(N, clientA); + this.u = Srp6Utilities.CalculateU(digest, N, A, pubB); + this.S = CalculateS(); + + return S; + } + + protected virtual BigInteger SelectPrivateValue() + { + return Srp6Utilities.GeneratePrivateValue(digest, N, g, random); + } + + private BigInteger CalculateS() + { + return v.ModPow(u, N).Multiply(A).Mod(N).ModPow(privB, N); + } + } +} diff --git a/src/core/srcbc/crypto/agreement/srp/SRP6Utilities.cs b/src/core/srcbc/crypto/agreement/srp/SRP6Utilities.cs new file mode 100644 index 0000000..c4d9326 --- /dev/null +++ b/src/core/srcbc/crypto/agreement/srp/SRP6Utilities.cs @@ -0,0 +1,85 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + public class Srp6Utilities + { + public static BigInteger CalculateK(IDigest digest, BigInteger N, BigInteger g) + { + return HashPaddedPair(digest, N, N, g); + } + + public static BigInteger CalculateU(IDigest digest, BigInteger N, BigInteger A, BigInteger B) + { + return HashPaddedPair(digest, N, A, B); + } + + public static BigInteger CalculateX(IDigest digest, BigInteger N, byte[] salt, byte[] identity, byte[] password) + { + byte[] output = new byte[digest.GetDigestSize()]; + + digest.BlockUpdate(identity, 0, identity.Length); + digest.Update((byte)':'); + digest.BlockUpdate(password, 0, password.Length); + digest.DoFinal(output, 0); + + digest.BlockUpdate(salt, 0, salt.Length); + digest.BlockUpdate(output, 0, output.Length); + digest.DoFinal(output, 0); + + return new BigInteger(1, output).Mod(N); + } + + public static BigInteger GeneratePrivateValue(IDigest digest, BigInteger N, BigInteger g, SecureRandom random) + { + int minBits = System.Math.Min(256, N.BitLength / 2); + BigInteger min = BigInteger.One.ShiftLeft(minBits - 1); + BigInteger max = N.Subtract(BigInteger.One); + + return BigIntegers.CreateRandomInRange(min, max, random); + } + + public static BigInteger ValidatePublicValue(BigInteger N, BigInteger val) + { + val = val.Mod(N); + + // Check that val % N != 0 + if (val.Equals(BigInteger.Zero)) + throw new CryptoException("Server credentials invalid"); + + return val; + } + + private static BigInteger HashPaddedPair(IDigest digest, BigInteger N, BigInteger n1, BigInteger n2) + { + int padLength = (N.BitLength + 7) / 8; + + byte[] n1_bytes = GetPadded(n1, padLength); + byte[] n2_bytes = GetPadded(n2, padLength); + + digest.BlockUpdate(n1_bytes, 0, n1_bytes.Length); + digest.BlockUpdate(n2_bytes, 0, n2_bytes.Length); + + byte[] output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + + return new BigInteger(1, output).Mod(N); + } + + private static byte[] GetPadded(BigInteger n, int length) + { + byte[] bs = BigIntegers.AsUnsignedByteArray(n); + if (bs.Length < length) + { + byte[] tmp = new byte[length]; + Array.Copy(bs, 0, tmp, length - bs.Length, bs.Length); + bs = tmp; + } + return bs; + } + } +} diff --git a/src/core/srcbc/crypto/agreement/srp/SRP6VerifierGenerator.cs b/src/core/srcbc/crypto/agreement/srp/SRP6VerifierGenerator.cs new file mode 100644 index 0000000..4edf65c --- /dev/null +++ b/src/core/srcbc/crypto/agreement/srp/SRP6VerifierGenerator.cs @@ -0,0 +1,49 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.Srp +{ + /** + * Generates new SRP verifier for user + */ + public class Srp6VerifierGenerator + { + protected BigInteger N; + protected BigInteger g; + protected IDigest digest; + + public Srp6VerifierGenerator() + { + } + + /** + * Initialises generator to create new verifiers + * @param N The safe prime to use (see DHParametersGenerator) + * @param g The group parameter to use (see DHParametersGenerator) + * @param digest The digest to use. The same digest type will need to be used later for the actual authentication + * attempt. Also note that the final session key size is dependent on the chosen digest. + */ + public virtual void Init(BigInteger N, BigInteger g, IDigest digest) + { + this.N = N; + this.g = g; + this.digest = digest; + } + + /** + * Creates a new SRP verifier + * @param salt The salt to use, generally should be large and random + * @param identity The user's identifying information (eg. username) + * @param password The user's password + * @return A new verifier for use in future SRP authentication + */ + public virtual BigInteger GenerateVerifier(byte[] salt, byte[] identity, byte[] password) + { + BigInteger x = Srp6Utilities.CalculateX(digest, N, salt, identity, password); + + return g.ModPow(x, N); + } + } +} + diff --git a/src/core/srcbc/crypto/digests/GeneralDigest.cs b/src/core/srcbc/crypto/digests/GeneralDigest.cs index 89f76f6..dea6acd 100644 --- a/src/core/srcbc/crypto/digests/GeneralDigest.cs +++ b/src/core/srcbc/crypto/digests/GeneralDigest.cs @@ -100,7 +100,7 @@ namespace Org.BouncyCastle.Crypto.Digests { byteCount = 0; xBufOff = 0; - for ( int i = 0; i < xBuf.Length; i++ ) xBuf[i] = 0; + Array.Clear(xBuf, 0, xBuf.Length); } public int GetByteLength() diff --git a/src/core/srcbc/crypto/digests/Sha256Digest.cs b/src/core/srcbc/crypto/digests/Sha256Digest.cs index de9c9da..b60fe63 100644 --- a/src/core/srcbc/crypto/digests/Sha256Digest.cs +++ b/src/core/srcbc/crypto/digests/Sha256Digest.cs @@ -17,16 +17,15 @@ namespace Org.BouncyCastle.Crypto.Digests public class Sha256Digest : GeneralDigest { - private const int DigestLength = 32; + private const int DigestLength = 32; - private int H1, H2, H3, H4, H5, H6, H7, H8; - - private int[] X = new int[64]; - private int xOff; + private uint H1, H2, H3, H4, H5, H6, H7, H8; + private uint[] X = new uint[64]; + private int xOff; public Sha256Digest() { - Reset(); + initHs(); } /** @@ -61,9 +60,11 @@ namespace Org.BouncyCastle.Crypto.Digests internal override void ProcessWord( byte[] input, int inOff) - { - X[xOff++] = ((input[inOff] & 0xff) << 24) | ((input[inOff + 1] & 0xff) << 16) - | ((input[inOff + 2] & 0xff) << 8) | ((input[inOff + 3] & 0xff)); + { + X[xOff++] = (((uint)input[inOff]) << 24) + | (((uint)input[inOff + 1]) << 16) + | (((uint)input[inOff + 2]) << 8) + | ((uint)input[inOff + 3]); if (xOff == 16) { @@ -72,26 +73,26 @@ namespace Org.BouncyCastle.Crypto.Digests } private void UnpackWord( - int word, + uint word, byte[] outBytes, int outOff) { - outBytes[outOff] = (byte)((uint) word >> 24); - outBytes[outOff + 1] = (byte)((uint) word >> 16); - outBytes[outOff + 2] = (byte)((uint) word >> 8); + outBytes[outOff] = (byte)(word >> 24); + outBytes[outOff + 1] = (byte)(word >> 16); + outBytes[outOff + 2] = (byte)(word >> 8); outBytes[outOff + 3] = (byte)word; } internal override void ProcessLength( - long bitLength) + long bitLength) { if (xOff > 14) { ProcessBlock(); } - X[14] = (int)((ulong) bitLength >> 32); - X[15] = (int)(bitLength & 0xffffffff); + X[14] = (uint)((ulong)bitLength >> 32); + X[15] = (uint)((ulong)bitLength); } public override int DoFinal( @@ -121,28 +122,27 @@ namespace Org.BouncyCastle.Crypto.Digests { base.Reset(); + initHs(); + + xOff = 0; + Array.Clear(X, 0, X.Length); + } + + private void initHs() + { /* SHA-256 initial hash value * The first 32 bits of the fractional parts of the square roots * of the first eight prime numbers */ - unchecked - { - H1 = (int) 0x6a09e667; - H2 = (int) 0xbb67ae85; - H3 = (int) 0x3c6ef372; - H4 = (int) 0xa54ff53a; - H5 = (int) 0x510e527f; - H6 = (int) 0x9b05688c; - H7 = (int) 0x1f83d9ab; - H8 = (int) 0x5be0cd19; - } - - xOff = 0; - for (int i = 0; i != X.Length; i++) - { - X[i] = 0; - } - } + H1 = 0x6a09e667; + H2 = 0xbb67ae85; + H3 = 0x3c6ef372; + H4 = 0xa54ff53a; + H5 = 0x510e527f; + H6 = 0x9b05688c; + H7 = 0x1f83d9ab; + H8 = 0x5be0cd19; + } internal override void ProcessBlock() { @@ -157,57 +157,57 @@ namespace Org.BouncyCastle.Crypto.Digests // // set up working variables. // - int a = H1; - int b = H2; - int c = H3; - int d = H4; - int e = H5; - int f = H6; - int g = H7; - int h = H8; + uint a = H1; + uint b = H2; + uint c = H3; + uint d = H4; + uint e = H5; + uint f = H6; + uint g = H7; + uint h = H8; int t = 0; - for(int i = 0; i < 8; i ++) + for(int i = 0; i < 8; ++i) { // t = 8 * i - h += Sum1(e) + Ch(e, f, g) + K[t] + X[t++]; + h += Sum1Ch(e, f, g) + K[t] + X[t++]; d += h; - h += Sum0(a) + Maj(a, b, c); + h += Sum0Maj(a, b, c); // t = 8 * i + 1 - g += Sum1(d) + Ch(d, e, f) + K[t] + X[t++]; + g += Sum1Ch(d, e, f) + K[t] + X[t++]; c += g; - g += Sum0(h) + Maj(h, a, b); + g += Sum0Maj(h, a, b); // t = 8 * i + 2 - f += Sum1(c) + Ch(c, d, e) + K[t] + X[t++]; + f += Sum1Ch(c, d, e) + K[t] + X[t++]; b += f; - f += Sum0(g) + Maj(g, h, a); + f += Sum0Maj(g, h, a); // t = 8 * i + 3 - e += Sum1(b) + Ch(b, c, d) + K[t] + X[t++]; + e += Sum1Ch(b, c, d) + K[t] + X[t++]; a += e; - e += Sum0(f) + Maj(f, g, h); + e += Sum0Maj(f, g, h); // t = 8 * i + 4 - d += Sum1(a) + Ch(a, b, c) + K[t] + X[t++]; + d += Sum1Ch(a, b, c) + K[t] + X[t++]; h += d; - d += Sum0(e) + Maj(e, f, g); + d += Sum0Maj(e, f, g); // t = 8 * i + 5 - c += Sum1(h) + Ch(h, a, b) + K[t] + X[t++]; + c += Sum1Ch(h, a, b) + K[t] + X[t++]; g += c; - c += Sum0(d) + Maj(d, e, f); + c += Sum0Maj(d, e, f); // t = 8 * i + 6 - b += Sum1(g) + Ch(g, h, a) + K[t] + X[t++]; + b += Sum1Ch(g, h, a) + K[t] + X[t++]; f += b; - b += Sum0(c) + Maj(c, d, e); + b += Sum0Maj(c, d, e); // t = 8 * i + 7 - a += Sum1(f) + Ch(f, g, h) + K[t] + X[t++]; + a += Sum1Ch(f, g, h) + K[t] + X[t++]; e += a; - a += Sum0(b) + Maj(b, c, d); + a += Sum0Maj(b, c, d); } H1 += a; @@ -227,84 +227,88 @@ namespace Org.BouncyCastle.Crypto.Digests Array.Clear(X, 0, 16); } - /* SHA-256 functions */ - private static int Ch( - int x, - int y, - int z) + private static uint Sum1Ch( + uint x, + uint y, + uint z) + { +// return Sum1(x) + Ch(x, y, z); + return (((x >> 6) | (x << 26)) ^ ((x >> 11) | (x << 21)) ^ ((x >> 25) | (x << 7))) + + ((x & y) ^ ((~x) & z)); + } + + private static uint Sum0Maj( + uint x, + uint y, + uint z) + { +// return Sum0(x) + Maj(x, y, z); + return (((x >> 2) | (x << 30)) ^ ((x >> 13) | (x << 19)) ^ ((x >> 22) | (x << 10))) + + ((x & y) ^ (x & z) ^ (y & z)); + } + +// /* SHA-256 functions */ +// private static uint Ch( +// uint x, +// uint y, +// uint z) +// { +// return ((x & y) ^ ((~x) & z)); +// } +// +// private static uint Maj( +// uint x, +// uint y, +// uint z) +// { +// return ((x & y) ^ (x & z) ^ (y & z)); +// } +// +// private static uint Sum0( +// uint x) +// { +// return ((x >> 2) | (x << 30)) ^ ((x >> 13) | (x << 19)) ^ ((x >> 22) | (x << 10)); +// } +// +// private static uint Sum1( +// uint x) +// { +// return ((x >> 6) | (x << 26)) ^ ((x >> 11) | (x << 21)) ^ ((x >> 25) | (x << 7)); +// } + + private static uint Theta0( + uint x) { - return ((x & y) ^ ((~x) & z)); + return ((x >> 7) | (x << 25)) ^ ((x >> 18) | (x << 14)) ^ (x >> 3); } - private static int Maj( - int x, - int y, - int z) + private static uint Theta1( + uint x) { - return ((x & y) ^ (x & z) ^ (y & z)); - } - - private static int Sum0( - int x) - { - return (((int)((uint)x >> 2)) | (x << 30)) ^ (((int)((uint)x >> 13)) | (x << 19)) ^ (((int)((uint)x >> 22)) | (x << 10)); - } - - private static int Sum1( - int x) - { - return (((int)((uint)x >> 6)) | (x << 26)) ^ (((int)((uint)x >> 11)) | (x << 21)) ^ (((int)((uint)x >> 25)) | (x << 7)); - } - - private static int Theta0( - int x) - { - return (((int)((uint)x >> 7)) | (x << 25)) ^ (((int)((uint)x >> 18)) | (x << 14)) ^ ((int)((uint)x >> 3)); - } - - private static int Theta1( - int x) - { - return (((int)((uint)x >> 17)) | (x << 15)) ^ (((int)((uint)x >> 19)) | (x << 13)) ^ ((int)((uint)x >> 10)); + return ((x >> 17) | (x << 15)) ^ ((x >> 19) | (x << 13)) ^ (x >> 10); } /* SHA-256 Constants * (represent the first 32 bits of the fractional parts of the * cube roots of the first sixty-four prime numbers) */ - internal static readonly int[] K = { - unchecked ((int) 0x428a2f98), unchecked ((int) 0x71374491), - unchecked ((int) 0xb5c0fbcf), unchecked ((int) 0xe9b5dba5), - unchecked ((int) 0x3956c25b), unchecked ((int) 0x59f111f1), - unchecked ((int) 0x923f82a4), unchecked ((int) 0xab1c5ed5), - unchecked ((int) 0xd807aa98), unchecked ((int) 0x12835b01), - unchecked ((int) 0x243185be), unchecked ((int) 0x550c7dc3), - unchecked ((int) 0x72be5d74), unchecked ((int) 0x80deb1fe), - unchecked ((int) 0x9bdc06a7), unchecked ((int) 0xc19bf174), - unchecked ((int) 0xe49b69c1), unchecked ((int) 0xefbe4786), - unchecked ((int) 0x0fc19dc6), unchecked ((int) 0x240ca1cc), - unchecked ((int) 0x2de92c6f), unchecked ((int) 0x4a7484aa), - unchecked ((int) 0x5cb0a9dc), unchecked ((int) 0x76f988da), - unchecked ((int) 0x983e5152), unchecked ((int) 0xa831c66d), - unchecked ((int) 0xb00327c8), unchecked ((int) 0xbf597fc7), - unchecked ((int) 0xc6e00bf3), unchecked ((int) 0xd5a79147), - unchecked ((int) 0x06ca6351), unchecked ((int) 0x14292967), - unchecked ((int) 0x27b70a85), unchecked ((int) 0x2e1b2138), - unchecked ((int) 0x4d2c6dfc), unchecked ((int) 0x53380d13), - unchecked ((int) 0x650a7354), unchecked ((int) 0x766a0abb), - unchecked ((int) 0x81c2c92e), unchecked ((int) 0x92722c85), - unchecked ((int) 0xa2bfe8a1), unchecked ((int) 0xa81a664b), - unchecked ((int) 0xc24b8b70), unchecked ((int) 0xc76c51a3), - unchecked ((int) 0xd192e819), unchecked ((int) 0xd6990624), - unchecked ((int) 0xf40e3585), unchecked ((int) 0x106aa070), - unchecked ((int) 0x19a4c116), unchecked ((int) 0x1e376c08), - unchecked ((int) 0x2748774c), unchecked ((int) 0x34b0bcb5), - unchecked ((int) 0x391c0cb3), unchecked ((int) 0x4ed8aa4a), - unchecked ((int) 0x5b9cca4f), unchecked ((int) 0x682e6ff3), - unchecked ((int) 0x748f82ee), unchecked ((int) 0x78a5636f), - unchecked ((int) 0x84c87814), unchecked ((int) 0x8cc70208), - unchecked ((int) 0x90befffa), unchecked ((int) 0xa4506ceb), - unchecked ((int) 0xbef9a3f7), unchecked ((int) 0xc67178f2) + private static readonly uint[] K = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; } } diff --git a/src/core/srcbc/crypto/engines/DesEngine.cs b/src/core/srcbc/crypto/engines/DesEngine.cs index 05368d2..9a29409 100644 --- a/src/core/srcbc/crypto/engines/DesEngine.cs +++ b/src/core/srcbc/crypto/engines/DesEngine.cs @@ -78,12 +78,12 @@ namespace Org.BouncyCastle.Crypto.Engines * Outerbridge's D3DES... */ - private static readonly short[] Df_Key = - { - 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, - 0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10, - 0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67 - }; +// private static readonly short[] Df_Key = +// { +// 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, +// 0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10, +// 0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67 +// }; private static readonly short[] bytebit = { diff --git a/src/core/srcbc/crypto/engines/TEAEngine.cs b/src/core/srcbc/crypto/engines/TEAEngine.cs index 4733282..beb25a4 100644 --- a/src/core/srcbc/crypto/engines/TEAEngine.cs +++ b/src/core/srcbc/crypto/engines/TEAEngine.cs @@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Crypto.Engines private const int rounds = 32, block_size = 8, - key_size = 16, +// key_size = 16, delta = unchecked((int) 0x9E3779B9), d_sum = unchecked((int) 0xC6EF3720); // sum on decrypt diff --git a/src/core/srcbc/crypto/engines/XTEAEngine.cs b/src/core/srcbc/crypto/engines/XTEAEngine.cs index 06d7549..a2cebb2 100644 --- a/src/core/srcbc/crypto/engines/XTEAEngine.cs +++ b/src/core/srcbc/crypto/engines/XTEAEngine.cs @@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Crypto.Engines private const int rounds = 32, block_size = 8, - key_size = 16, +// key_size = 16, delta = unchecked((int) 0x9E3779B9); /* diff --git a/src/core/srcbc/crypto/generators/DHBasicKeyPairGenerator.cs b/src/core/srcbc/crypto/generators/DHBasicKeyPairGenerator.cs index 843587e..80b4bf0 100644 --- a/src/core/srcbc/crypto/generators/DHBasicKeyPairGenerator.cs +++ b/src/core/srcbc/crypto/generators/DHBasicKeyPairGenerator.cs @@ -6,10 +6,10 @@ using Org.BouncyCastle.Math; namespace Org.BouncyCastle.Crypto.Generators { /** - * a basic Diffie-Helman key pair generator. + * a basic Diffie-Hellman key pair generator. * - * This Generates keys consistent for use with the basic algorithm for - * Diffie-Helman. + * This generates keys consistent for use with the basic algorithm for + * Diffie-Hellman. */ public class DHBasicKeyPairGenerator : IAsymmetricCipherKeyPairGenerator @@ -19,22 +19,20 @@ namespace Org.BouncyCastle.Crypto.Generators public virtual void Init( KeyGenerationParameters parameters) { - this.param = (DHKeyGenerationParameters) parameters; + this.param = (DHKeyGenerationParameters)parameters; } public virtual AsymmetricCipherKeyPair GenerateKeyPair() { DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.Instance; - DHParameters dhParams = param.Parameters; + DHParameters dhp = param.Parameters; - BigInteger p = dhParams.P; - BigInteger x = helper.CalculatePrivate(p, param.Random, dhParams.L); - BigInteger y = helper.CalculatePublic(p, dhParams.G, x); + BigInteger x = helper.CalculatePrivate(dhp, param.Random); + BigInteger y = helper.CalculatePublic(dhp, x); return new AsymmetricCipherKeyPair( - new DHPublicKeyParameters(y, dhParams), - new DHPrivateKeyParameters(x, dhParams)); + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); } } - } diff --git a/src/core/srcbc/crypto/generators/DHKeyGeneratorHelper.cs b/src/core/srcbc/crypto/generators/DHKeyGeneratorHelper.cs index 4da5985..5391614 100644 --- a/src/core/srcbc/crypto/generators/DHKeyGeneratorHelper.cs +++ b/src/core/srcbc/crypto/generators/DHKeyGeneratorHelper.cs @@ -1,14 +1,14 @@ using System; +using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Generators { class DHKeyGeneratorHelper { - private const int MAX_ITERATIONS = 1000; - internal static readonly DHKeyGeneratorHelper Instance = new DHKeyGeneratorHelper(); private DHKeyGeneratorHelper() @@ -16,63 +16,38 @@ namespace Org.BouncyCastle.Crypto.Generators } internal BigInteger CalculatePrivate( - BigInteger p, - SecureRandom random, - int limit) - { - // - // calculate the private key - // - BigInteger pSub2 = p.Subtract(BigInteger.Two); - BigInteger x; - - if (limit == 0) - { - x = createInRange(pSub2, random); - } - else - { - do - { - // TODO Check this (should the generated numbers always be odd, - // and length 'limit'?) - x = new BigInteger(limit, 0, random); - } - while (x.SignValue == 0); - } - - return x; - } - - private BigInteger createInRange( - BigInteger max, + DHParameters dhParams, SecureRandom random) { - BigInteger x; - int maxLength = max.BitLength; - int count = 0; + int limit = dhParams.L; - do + if (limit != 0) { - x = new BigInteger(maxLength, random); - count++; - } - while ((x.SignValue == 0 || x.CompareTo(max) > 0) && count != MAX_ITERATIONS); - - if (count == MAX_ITERATIONS) // fall back to a faster (restricted) method - { - return new BigInteger(maxLength - 1, random).SetBit(0); + return new BigInteger(limit, random).SetBit(limit - 1); } - return x; + BigInteger min = BigInteger.Two; + int m = dhParams.M; + if (m != 0) + { + min = BigInteger.One.ShiftLeft(m - 1); + } + + BigInteger max = dhParams.P.Subtract(BigInteger.Two); + BigInteger q = dhParams.Q; + if (q != null) + { + max = q.Subtract(BigInteger.Two); + } + + return BigIntegers.CreateRandomInRange(min, max, random); } internal BigInteger CalculatePublic( - BigInteger p, - BigInteger g, - BigInteger x) + DHParameters dhParams, + BigInteger x) { - return g.ModPow(x, p); + return dhParams.G.ModPow(x, dhParams.P); } } } diff --git a/src/core/srcbc/crypto/generators/DHKeyPairGenerator.cs b/src/core/srcbc/crypto/generators/DHKeyPairGenerator.cs index d22ffe7..cfc09a2 100644 --- a/src/core/srcbc/crypto/generators/DHKeyPairGenerator.cs +++ b/src/core/srcbc/crypto/generators/DHKeyPairGenerator.cs @@ -6,9 +6,9 @@ using Org.BouncyCastle.Math; namespace Org.BouncyCastle.Crypto.Generators { /** - * a Diffie-Helman key pair generator. + * a Diffie-Hellman key pair generator. * - * This Generates keys consistent for use in the MTI/A0 key agreement protocol + * This generates keys consistent for use in the MTI/A0 key agreement protocol * as described in "Handbook of Applied Cryptography", Pages 516-519. */ public class DHKeyPairGenerator @@ -19,22 +19,20 @@ namespace Org.BouncyCastle.Crypto.Generators public virtual void Init( KeyGenerationParameters parameters) { - this.param = (DHKeyGenerationParameters) parameters; + this.param = (DHKeyGenerationParameters)parameters; } public virtual AsymmetricCipherKeyPair GenerateKeyPair() { DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.Instance; - DHParameters dhParams = param.Parameters; + DHParameters dhp = param.Parameters; - BigInteger p = dhParams.P; - BigInteger x = helper.CalculatePrivate(p, param.Random, dhParams.L); - BigInteger y = helper.CalculatePublic(p, dhParams.G, x); + BigInteger x = helper.CalculatePrivate(dhp, param.Random); + BigInteger y = helper.CalculatePublic(dhp, x); return new AsymmetricCipherKeyPair( - new DHPublicKeyParameters(y, dhParams), - new DHPrivateKeyParameters(x, dhParams)); + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); } } - } diff --git a/src/core/srcbc/crypto/generators/DHParametersHelper.cs b/src/core/srcbc/crypto/generators/DHParametersHelper.cs index 25076c1..092d638 100644 --- a/src/core/srcbc/crypto/generators/DHParametersHelper.cs +++ b/src/core/srcbc/crypto/generators/DHParametersHelper.cs @@ -2,6 +2,7 @@ using System; using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Generators { @@ -208,7 +209,7 @@ namespace Org.BouncyCastle.Crypto.Generators // Handbook of Applied Cryptography 4.86 do { - g = CreateInRange(BigInteger.Two, pMinusTwo, random); + g = BigIntegers.CreateRandomInRange(BigInteger.Two, pMinusTwo, random); } while (g.ModPow(BigInteger.Two, p).Equals(BigInteger.One) || g.ModPow(q, p).Equals(BigInteger.One)); @@ -226,19 +227,5 @@ namespace Org.BouncyCastle.Crypto.Generators return g; } - - private static BigInteger CreateInRange( - BigInteger min, - BigInteger max, - SecureRandom random) - { - BigInteger x; - do - { - x = new BigInteger(max.BitLength, random); - } - while (x.CompareTo(min) < 0 || x.CompareTo(max) > 0); - return x; - } } } diff --git a/src/core/srcbc/crypto/generators/DesEdeKeyGenerator.cs b/src/core/srcbc/crypto/generators/DesEdeKeyGenerator.cs index 10ef4e7..d4de2f9 100644 --- a/src/core/srcbc/crypto/generators/DesEdeKeyGenerator.cs +++ b/src/core/srcbc/crypto/generators/DesEdeKeyGenerator.cs @@ -29,7 +29,8 @@ namespace Org.BouncyCastle.Crypto.Generators protected override void engineInit( KeyGenerationParameters parameters) { - base.engineInit(parameters); + this.random = parameters.Random; + this.strength = (parameters.Strength + 7) / 8; if (strength == 0 || strength == (168 / 8)) { diff --git a/src/core/srcbc/crypto/generators/DesKeyGenerator.cs b/src/core/srcbc/crypto/generators/DesKeyGenerator.cs index 12dcc9d..f151887 100644 --- a/src/core/srcbc/crypto/generators/DesKeyGenerator.cs +++ b/src/core/srcbc/crypto/generators/DesKeyGenerator.cs @@ -17,6 +17,29 @@ namespace Org.BouncyCastle.Crypto.Generators { } + /** + * initialise the key generator - if strength is set to zero + * the key generated will be 64 bits in size, otherwise + * strength can be 64 or 56 bits (if you don't count the parity bits). + * + * @param param the parameters to be used for key generation + */ + protected override void engineInit( + KeyGenerationParameters parameters) + { + base.engineInit(parameters); + + if (strength == 0 || strength == (56 / 8)) + { + strength = DesParameters.DesKeyLength; + } + else if (strength != DesParameters.DesKeyLength) + { + throw new ArgumentException( + "DES key must be " + (DesParameters.DesKeyLength * 8) + " bits long."); + } + } + protected override byte[] engineGenerateKey() { byte[] newKey; diff --git a/src/core/srcbc/crypto/generators/ECKeyPairGenerator.cs b/src/core/srcbc/crypto/generators/ECKeyPairGenerator.cs index 7d7718c..e090eeb 100644 --- a/src/core/srcbc/crypto/generators/ECKeyPairGenerator.cs +++ b/src/core/srcbc/crypto/generators/ECKeyPairGenerator.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; @@ -59,12 +60,21 @@ namespace Org.BouncyCastle.Crypto.Generators case 192: oid = X9ObjectIdentifiers.Prime192v1; break; + case 224: + oid = SecObjectIdentifiers.SecP224r1; + break; case 239: oid = X9ObjectIdentifiers.Prime239v1; break; case 256: oid = X9ObjectIdentifiers.Prime256v1; break; + case 384: + oid = SecObjectIdentifiers.SecP384r1; + break; + case 521: + oid = SecObjectIdentifiers.SecP521r1; + break; default: throw new InvalidParameterException("unknown key size."); } diff --git a/src/core/srcbc/crypto/generators/ElGamalKeyPairGenerator.cs b/src/core/srcbc/crypto/generators/ElGamalKeyPairGenerator.cs index 9e3f54d..f8a879b 100644 --- a/src/core/srcbc/crypto/generators/ElGamalKeyPairGenerator.cs +++ b/src/core/srcbc/crypto/generators/ElGamalKeyPairGenerator.cs @@ -25,15 +25,15 @@ namespace Org.BouncyCastle.Crypto.Generators public AsymmetricCipherKeyPair GenerateKeyPair() { DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.Instance; - ElGamalParameters elParams = param.Parameters; + ElGamalParameters egp = param.Parameters; + DHParameters dhp = new DHParameters(egp.P, egp.G, null, 0, egp.L); - BigInteger p = elParams.P; - BigInteger x = helper.CalculatePrivate(p, param.Random, elParams.L); - BigInteger y = helper.CalculatePublic(p, elParams.G, x); + BigInteger x = helper.CalculatePrivate(dhp, param.Random); + BigInteger y = helper.CalculatePublic(dhp, x); return new AsymmetricCipherKeyPair( - new ElGamalPublicKeyParameters(y, elParams), - new ElGamalPrivateKeyParameters(x, elParams)); + new ElGamalPublicKeyParameters(y, egp), + new ElGamalPrivateKeyParameters(x, egp)); } } diff --git a/src/core/srcbc/crypto/io/DigestStream.cs b/src/core/srcbc/crypto/io/DigestStream.cs index 6a5ab42..8820ea8 100644 --- a/src/core/srcbc/crypto/io/DigestStream.cs +++ b/src/core/srcbc/crypto/io/DigestStream.cs @@ -1,116 +1,137 @@ using System; using System.IO; -using Org.BouncyCastle.Crypto; + namespace Org.BouncyCastle.Crypto.IO { - public class DigestStream : Stream - { - internal Stream stream; - internal IDigest inDigest; - internal IDigest outDigest; + public class DigestStream + : Stream + { + protected readonly Stream stream; + protected readonly IDigest inDigest; + protected readonly IDigest outDigest; - public DigestStream( - Stream stream, - IDigest readDigest, - IDigest writeDigest) - { - this.stream = stream; - this.inDigest = readDigest; - this.outDigest = writeDigest; - } - public IDigest ReadDigest() - { - return inDigest; - } - public IDigest WriteDigest() - { - return outDigest; - } - public override int ReadByte() - { - int b = stream.ReadByte(); - if (inDigest != null) - { - if (b >= 0) - { - inDigest.Update((byte)b); - } - } - return b; - } + public DigestStream( + Stream stream, + IDigest readDigest, + IDigest writeDigest) + { + this.stream = stream; + this.inDigest = readDigest; + this.outDigest = writeDigest; + } - public override int Read(byte[] buffer, int offset, int count) - { - int n = stream.Read(buffer, offset, count); - if (inDigest != null) - { - if (n > 0) - { - inDigest.BlockUpdate(buffer, offset, n); - } - } - return n; - } - public override void Write( - byte[] buffer, - int offset, - int count) - { - if (outDigest != null) - { - if (count > 0) - { - outDigest.BlockUpdate(buffer, offset, count); - } - } - stream.Write(buffer, offset, count); - } - public override void WriteByte(byte value) - { - if (outDigest != null) - { - outDigest.Update(value); - } - stream.WriteByte(value); - } - public override bool CanRead - { - get { return stream.CanRead && (inDigest != null); } - } - public override bool CanWrite - { - get { return stream.CanWrite && (outDigest != null); } - } - public override bool CanSeek - { - get { return stream.CanSeek; } - } - public override long Length - { - get { return stream.Length; } - } - public override long Position - { - get { return stream.Position; } - set { stream.Position = value; } - } - public override void Close() - { - stream.Close(); - } - public override void Flush() - { - stream.Flush(); - } - public override long Seek( - long offset, - SeekOrigin origin) - { - return stream.Seek(offset,origin); - } - public override void SetLength(long value) - { - stream.SetLength(value); - } - } + public virtual IDigest ReadDigest() + { + return inDigest; + } + + public virtual IDigest WriteDigest() + { + return outDigest; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + int n = stream.Read(buffer, offset, count); + if (inDigest != null) + { + if (n > 0) + { + inDigest.BlockUpdate(buffer, offset, n); + } + } + return n; + } + + public override int ReadByte() + { + int b = stream.ReadByte(); + if (inDigest != null) + { + if (b >= 0) + { + inDigest.Update((byte)b); + } + } + return b; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (outDigest != null) + { + if (count > 0) + { + outDigest.BlockUpdate(buffer, offset, count); + } + } + stream.Write(buffer, offset, count); + } + + public override void WriteByte( + byte b) + { + if (outDigest != null) + { + outDigest.Update(b); + } + stream.WriteByte(b); + } + + public override bool CanRead + { + get { return stream.CanRead; } + } + + public override bool CanWrite + { + get { return stream.CanWrite; } + } + + public override bool CanSeek + { + get { return stream.CanSeek; } + } + + public override long Length + { + get { return stream.Length; } + } + + public override long Position + { + get { return stream.Position; } + set { stream.Position = value; } + } + + public override void Close() + { + stream.Close(); + } + + public override void Flush() + { + stream.Flush(); + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + return stream.Seek(offset, origin); + } + + public override void SetLength( + long length) + { + stream.SetLength(length); + } + } } + diff --git a/src/core/srcbc/crypto/io/MacStream.cs b/src/core/srcbc/crypto/io/MacStream.cs index 5f87b79..aa32c7b 100644 --- a/src/core/srcbc/crypto/io/MacStream.cs +++ b/src/core/srcbc/crypto/io/MacStream.cs @@ -1,116 +1,136 @@ using System; using System.IO; -using Org.BouncyCastle.Crypto; + namespace Org.BouncyCastle.Crypto.IO { - public class MacStream : Stream - { - internal Stream stream; - internal IMac inMac; - internal IMac outMac; + public class MacStream + : Stream + { + protected readonly Stream stream; + protected readonly IMac inMac; + protected readonly IMac outMac; - public MacStream( - Stream stream, - IMac readMac, - IMac writeMac) - { - this.stream = stream; - this.inMac = readMac; - this.outMac = writeMac; - } - public IMac ReadMac() - { - return inMac; - } - public IMac WriteMac() - { - return outMac; - } - public override int ReadByte() - { - int b = stream.ReadByte(); - if (inMac != null) - { - if (b >= 0) - { - inMac.Update((byte)b); - } - } - return b; - } + public MacStream( + Stream stream, + IMac readMac, + IMac writeMac) + { + this.stream = stream; + this.inMac = readMac; + this.outMac = writeMac; + } - public override int Read(byte[] buffer, int offset, int count) - { - int n = stream.Read(buffer, offset, count); - if (inMac != null) - { - if (n > 0) - { - inMac.BlockUpdate(buffer, offset, count); - } - } - return n; - } - public override void Write( - byte[] buffer, - int offset, - int count) - { - if (outMac != null) - { - if (count > 0) - { - outMac.BlockUpdate(buffer, offset, count); - } - } - stream.Write(buffer, offset, count); - } - public override void WriteByte(byte value) - { - if (outMac != null) - { - outMac.Update(value); - } - stream.WriteByte(value); - } - public override bool CanRead - { - get { return stream.CanRead && (inMac != null); } - } - public override bool CanWrite - { - get { return stream.CanWrite && (outMac != null); } - } - public override bool CanSeek - { - get { return stream.CanSeek; } - } - public override long Length - { - get { return stream.Length; } - } - public override long Position - { - get { return stream.Position; } - set { stream.Position = value; } - } - public override void Close() - { - stream.Close(); - } - public override void Flush() - { - stream.Flush(); - } - public override long Seek( - long offset, - SeekOrigin origin) - { - return stream.Seek(offset,origin); - } - public override void SetLength(long value) - { - stream.SetLength(value); - } - } + public virtual IMac ReadMac() + { + return inMac; + } + + public virtual IMac WriteMac() + { + return outMac; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + int n = stream.Read(buffer, offset, count); + if (inMac != null) + { + if (n > 0) + { + inMac.BlockUpdate(buffer, offset, count); + } + } + return n; + } + + public override int ReadByte() + { + int b = stream.ReadByte(); + if (inMac != null) + { + if (b >= 0) + { + inMac.Update((byte)b); + } + } + return b; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (outMac != null) + { + if (count > 0) + { + outMac.BlockUpdate(buffer, offset, count); + } + } + stream.Write(buffer, offset, count); + } + + public override void WriteByte(byte b) + { + if (outMac != null) + { + outMac.Update(b); + } + stream.WriteByte(b); + } + + public override bool CanRead + { + get { return stream.CanRead; } + } + + public override bool CanWrite + { + get { return stream.CanWrite; } + } + + public override bool CanSeek + { + get { return stream.CanSeek; } + } + + public override long Length + { + get { return stream.Length; } + } + + public override long Position + { + get { return stream.Position; } + set { stream.Position = value; } + } + + public override void Close() + { + stream.Close(); + } + + public override void Flush() + { + stream.Flush(); + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + return stream.Seek(offset,origin); + } + + public override void SetLength( + long length) + { + stream.SetLength(length); + } + } } + diff --git a/src/core/srcbc/crypto/io/SignerStream.cs b/src/core/srcbc/crypto/io/SignerStream.cs new file mode 100644 index 0000000..bb1434c --- /dev/null +++ b/src/core/srcbc/crypto/io/SignerStream.cs @@ -0,0 +1,137 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.IO +{ + public class SignerStream + : Stream + { + protected readonly Stream stream; + protected readonly ISigner inSigner; + protected readonly ISigner outSigner; + + public SignerStream( + Stream stream, + ISigner readSigner, + ISigner writeSigner) + { + this.stream = stream; + this.inSigner = readSigner; + this.outSigner = writeSigner; + } + + public virtual ISigner ReadSigner() + { + return inSigner; + } + + public virtual ISigner WriteSigner() + { + return outSigner; + } + + public override int Read( + byte[] buffer, + int offset, + int count) + { + int n = stream.Read(buffer, offset, count); + if (inSigner != null) + { + if (n > 0) + { + inSigner.BlockUpdate(buffer, offset, n); + } + } + return n; + } + + public override int ReadByte() + { + int b = stream.ReadByte(); + if (inSigner != null) + { + if (b >= 0) + { + inSigner.Update((byte)b); + } + } + return b; + } + + public override void Write( + byte[] buffer, + int offset, + int count) + { + if (outSigner != null) + { + if (count > 0) + { + outSigner.BlockUpdate(buffer, offset, count); + } + } + stream.Write(buffer, offset, count); + } + + public override void WriteByte( + byte b) + { + if (outSigner != null) + { + outSigner.Update(b); + } + stream.WriteByte(b); + } + + public override bool CanRead + { + get { return stream.CanRead; } + } + + public override bool CanWrite + { + get { return stream.CanWrite; } + } + + public override bool CanSeek + { + get { return stream.CanSeek; } + } + + public override long Length + { + get { return stream.Length; } + } + + public override long Position + { + get { return stream.Position; } + set { stream.Position = value; } + } + + public override void Close() + { + stream.Close(); + } + + public override void Flush() + { + stream.Flush(); + } + + public override long Seek( + long offset, + SeekOrigin origin) + { + return stream.Seek(offset, origin); + } + + public override void SetLength( + long length) + { + stream.SetLength(length); + } + } +} + diff --git a/src/core/srcbc/crypto/parameters/DHKeyGenerationParameters.cs b/src/core/srcbc/crypto/parameters/DHKeyGenerationParameters.cs index f83dfaf..cb33b0f 100644 --- a/src/core/srcbc/crypto/parameters/DHKeyGenerationParameters.cs +++ b/src/core/srcbc/crypto/parameters/DHKeyGenerationParameters.cs @@ -12,7 +12,7 @@ namespace Org.BouncyCastle.Crypto.Parameters public DHKeyGenerationParameters( SecureRandom random, DHParameters parameters) - : base(random, parameters.P.BitLength) + : base(random, GetStrength(parameters)) { this.parameters = parameters; } @@ -21,5 +21,11 @@ namespace Org.BouncyCastle.Crypto.Parameters { get { return parameters; } } + + internal static int GetStrength( + DHParameters parameters) + { + return parameters.L != 0 ? parameters.L : parameters.P.BitLength; + } } } diff --git a/src/core/srcbc/crypto/parameters/DHParameters.cs b/src/core/srcbc/crypto/parameters/DHParameters.cs index 575e096..7e55e27 100644 --- a/src/core/srcbc/crypto/parameters/DHParameters.cs +++ b/src/core/srcbc/crypto/parameters/DHParameters.cs @@ -14,21 +14,19 @@ namespace Org.BouncyCastle.Crypto.Parameters private readonly int m, l; private readonly DHValidationParameters validation; - private static int GetDefaultM( - BigInteger p, - int l) + private static int GetDefaultMParam( + int lParam) { - int effectiveL = l != 0 ? l : p.BitLength - 1; + if (lParam == 0) + return DefaultMinimumLength; - return System.Math.Min(DefaultMinimumLength, effectiveL); - -// return DefaultMinimumLength; + return System.Math.Min(lParam, DefaultMinimumLength); } public DHParameters( BigInteger p, BigInteger g) - : this(p, g, null, GetDefaultM(p, 0), 0, null, null) + : this(p, g, null, 0) { } @@ -36,7 +34,7 @@ namespace Org.BouncyCastle.Crypto.Parameters BigInteger p, BigInteger g, BigInteger q) - : this(p, g, q, GetDefaultM(p, 0), 0, null, null) + : this(p, g, q, 0) { } @@ -45,9 +43,9 @@ namespace Org.BouncyCastle.Crypto.Parameters BigInteger g, BigInteger q, int l) - : this(p, g, q, GetDefaultM(p, l), l, null, null) + : this(p, g, q, GetDefaultMParam(l), l, null, null) { - } + } public DHParameters( BigInteger p, @@ -65,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Parameters BigInteger q, BigInteger j, DHValidationParameters validation) - : this(p, g, q, GetDefaultM(p, 0), 0, j, validation) + : this(p, g, q, DefaultMinimumLength, 0, j, validation) { } @@ -87,13 +85,22 @@ namespace Org.BouncyCastle.Crypto.Parameters if (g.CompareTo(BigInteger.Two) < 0 || g.CompareTo(p.Subtract(BigInteger.Two)) > 0) throw new ArgumentException("generator must in the range [2, p - 2]", "g"); + if (q != null && q.BitLength >= p.BitLength) + throw new ArgumentException("q too big to be a factor of (p-1)", "q"); if (m >= p.BitLength) throw new ArgumentException("m value must be < bitlength of p", "m"); - if (l != 0 && l < m) - throw new ArgumentException("l value must be >= m, or zero", "l"); + if (l != 0) + { + if (l >= p.BitLength) + throw new ArgumentException("when l value specified, it must be less than bitlength(p)", "l"); + if (l < m) + throw new ArgumentException("when l value specified, it may not be less than m value", "l"); + } if (j != null && j.CompareTo(BigInteger.Two) < 0) throw new ArgumentException("subgroup factor must be >= 2", "j"); + // TODO If q, j both provided, validate p = jq + 1 ? + this.p = p; this.g = g; this.q = q; @@ -130,7 +137,6 @@ namespace Org.BouncyCastle.Crypto.Parameters } /// The bitlength of the private value. - /// If zero, bitLength(p) - 1 will be used. public int L { get { return l; } diff --git a/src/core/srcbc/crypto/parameters/ElGamalKeyGenerationParameters.cs b/src/core/srcbc/crypto/parameters/ElGamalKeyGenerationParameters.cs index fb00bf6..6d4fd79 100644 --- a/src/core/srcbc/crypto/parameters/ElGamalKeyGenerationParameters.cs +++ b/src/core/srcbc/crypto/parameters/ElGamalKeyGenerationParameters.cs @@ -12,7 +12,7 @@ namespace Org.BouncyCastle.Crypto.Parameters public ElGamalKeyGenerationParameters( SecureRandom random, ElGamalParameters parameters) - : base(random, parameters.P.BitLength) + : base(random, GetStrength(parameters)) { this.parameters = parameters; } @@ -21,5 +21,11 @@ namespace Org.BouncyCastle.Crypto.Parameters { get { return parameters; } } + + internal static int GetStrength( + ElGamalParameters parameters) + { + return parameters.L != 0 ? parameters.L : parameters.P.BitLength; + } } } diff --git a/src/core/srcbc/crypto/signers/DsaDigestSigner.cs b/src/core/srcbc/crypto/signers/DsaDigestSigner.cs index a807e34..815c9cc 100644 --- a/src/core/srcbc/crypto/signers/DsaDigestSigner.cs +++ b/src/core/srcbc/crypto/signers/DsaDigestSigner.cs @@ -49,14 +49,10 @@ namespace Org.BouncyCastle.Crypto.Signers } if (forSigning && !k.IsPrivate) - { throw new InvalidKeyException("Signing Requires Private Key."); - } if (!forSigning && k.IsPrivate) - { throw new InvalidKeyException("Verification Requires Public Key."); - } Reset(); @@ -95,16 +91,9 @@ namespace Org.BouncyCastle.Crypto.Signers byte[] hash = new byte[digest.GetDigestSize()]; digest.DoFinal(hash, 0); - try - { - BigInteger[] sig = dsaSigner.GenerateSignature(hash); + BigInteger[] sig = dsaSigner.GenerateSignature(hash); - return DerEncode(sig[0], sig[1]); - } - catch (Exception e) - { - throw new SignatureException(e.Message, e); - } + return DerEncode(sig[0], sig[1]); } /// true if the internal state represents the signature described in the passed in array. @@ -117,17 +106,15 @@ namespace Org.BouncyCastle.Crypto.Signers byte[] hash = new byte[digest.GetDigestSize()]; digest.DoFinal(hash, 0); - BigInteger[] sig; try { - sig = DerDecode(signature); + BigInteger[] sig = DerDecode(signature); + return dsaSigner.VerifySignature(hash, sig[0], sig[1]); } - catch (Exception e) + catch (IOException) { - throw new SignatureException("error decoding signature bytes.", e); + return false; } - - return dsaSigner.VerifySignature(hash, sig[0], sig[1]); } /// Reset the internal state diff --git a/src/core/srcbc/crypto/signers/GenericSigner.cs b/src/core/srcbc/crypto/signers/GenericSigner.cs new file mode 100644 index 0000000..6aa022e --- /dev/null +++ b/src/core/srcbc/crypto/signers/GenericSigner.cs @@ -0,0 +1,129 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Signers +{ + public class GenericSigner + : ISigner + { + private readonly IAsymmetricBlockCipher engine; + private readonly IDigest digest; + private bool forSigning; + + public GenericSigner( + IAsymmetricBlockCipher engine, + IDigest digest) + { + this.engine = engine; + this.digest = digest; + } + + public string AlgorithmName + { + get { return "Generic(" + engine.AlgorithmName + "/" + digest.AlgorithmName + ")"; } + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void Init( + bool forSigning, + ICipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters is ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).Parameters; + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.IsPrivate) + throw new InvalidKeyException("Signing requires private key."); + + if (!forSigning && k.IsPrivate) + throw new InvalidKeyException("Verification requires public key."); + + Reset(); + + engine.Init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void Update( + byte input) + { + digest.Update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void BlockUpdate( + byte[] input, + int inOff, + int length) + { + digest.BlockUpdate(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] GenerateSignature() + { + if (!forSigning) + throw new InvalidOperationException("GenericSigner not initialised for signature generation."); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + return engine.ProcessBlock(hash, 0, hash.Length); + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public bool VerifySignature( + byte[] signature) + { + if (forSigning) + throw new InvalidOperationException("GenericSigner not initialised for verification"); + + byte[] hash = new byte[digest.GetDigestSize()]; + digest.DoFinal(hash, 0); + + try + { + byte[] sig = engine.ProcessBlock(signature, 0, signature.Length); + + return Arrays.AreEqual(sig, hash); + } + catch (Exception) + { + return false; + } + } + + public void Reset() + { + digest.Reset(); + } + } +} diff --git a/src/core/srcbc/crypto/tls/TlsCipherSuiteManager.cs b/src/core/srcbc/crypto/tls/TlsCipherSuiteManager.cs index 505841d..970ebad 100644 --- a/src/core/srcbc/crypto/tls/TlsCipherSuiteManager.cs +++ b/src/core/srcbc/crypto/tls/TlsCipherSuiteManager.cs @@ -14,25 +14,36 @@ namespace Org.BouncyCastle.Crypto.Tls public class TlsCipherSuiteManager { private const int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a; + private const int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013; private const int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016; private const int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f; + private const int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032; private const int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033; private const int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035; + private const int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038; private const int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039; internal static void WriteCipherSuites( Stream outStr) { - TlsUtilities.WriteUint16(2 * 6, outStr); - - TlsUtilities.WriteUint16(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, outStr); - TlsUtilities.WriteUint16(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, outStr); - TlsUtilities.WriteUint16(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, outStr); - - TlsUtilities.WriteUint16(TLS_RSA_WITH_AES_256_CBC_SHA, outStr); - TlsUtilities.WriteUint16(TLS_RSA_WITH_AES_128_CBC_SHA, outStr); - TlsUtilities.WriteUint16(TLS_RSA_WITH_3DES_EDE_CBC_SHA, outStr); + int[] suites = new int[] + { + TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }; + TlsUtilities.WriteUint16(2 * suites.Length, outStr); + for (int i = 0; i < suites.Length; ++i) + { + TlsUtilities.WriteUint16(suites[i], outStr); + } } internal static TlsCipherSuite GetCipherSuite( @@ -44,18 +55,27 @@ namespace Org.BouncyCastle.Crypto.Tls case TLS_RSA_WITH_3DES_EDE_CBC_SHA: return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new DesEdeEngine()), new CbcBlockCipher(new DesEdeEngine()), new Sha1Digest(), new Sha1Digest(), 24, TlsCipherSuite.KE_RSA); + case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new DesEdeEngine()), new CbcBlockCipher(new DesEdeEngine()), new Sha1Digest(), new Sha1Digest(), 24, TlsCipherSuite.KE_DHE_DSS); + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new DesEdeEngine()), new CbcBlockCipher(new DesEdeEngine()), new Sha1Digest(), new Sha1Digest(), 24, TlsCipherSuite.KE_DHE_RSA); case TLS_RSA_WITH_AES_128_CBC_SHA: return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new AesFastEngine()), new CbcBlockCipher(new AesFastEngine()), new Sha1Digest(), new Sha1Digest(), 16, TlsCipherSuite.KE_RSA); + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new AesFastEngine()), new CbcBlockCipher(new AesFastEngine()), new Sha1Digest(), new Sha1Digest(), 16, TlsCipherSuite.KE_DHE_DSS); + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new AesFastEngine()), new CbcBlockCipher(new AesFastEngine()), new Sha1Digest(), new Sha1Digest(), 16, TlsCipherSuite.KE_DHE_RSA); case TLS_RSA_WITH_AES_256_CBC_SHA: return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new AesFastEngine()), new CbcBlockCipher(new AesFastEngine()), new Sha1Digest(), new Sha1Digest(), 32, TlsCipherSuite.KE_RSA); + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new AesFastEngine()), new CbcBlockCipher(new AesFastEngine()), new Sha1Digest(), new Sha1Digest(), 32, TlsCipherSuite.KE_DHE_DSS); + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: return new TlsBlockCipherCipherSuite(new CbcBlockCipher(new AesFastEngine()), new CbcBlockCipher(new AesFastEngine()), new Sha1Digest(), new Sha1Digest(), 32, TlsCipherSuite.KE_DHE_RSA); diff --git a/src/core/srcbc/crypto/tls/TlsDssSigner.cs b/src/core/srcbc/crypto/tls/TlsDssSigner.cs new file mode 100644 index 0000000..0e58e0b --- /dev/null +++ b/src/core/srcbc/crypto/tls/TlsDssSigner.cs @@ -0,0 +1,16 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Signers; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsDssSigner + : DsaDigestSigner + { + internal TlsDssSigner() + : base(new DsaSigner(), new Sha1Digest()) + { + } + } +} diff --git a/src/core/srcbc/crypto/tls/TlsProtocolHandler.cs b/src/core/srcbc/crypto/tls/TlsProtocolHandler.cs index d714752..1e59f12 100644 --- a/src/core/srcbc/crypto/tls/TlsProtocolHandler.cs +++ b/src/core/srcbc/crypto/tls/TlsProtocolHandler.cs @@ -1,1152 +1,1219 @@ -using System; -using System.IO; -using System.Text; - -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto.Encodings; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Prng; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities.Date; - -namespace Org.BouncyCastle.Crypto.Tls -{ - /// An implementation of all high level protocols in TLS 1.0. - public class TlsProtocolHandler - { - private const short RL_CHANGE_CIPHER_SPEC = 20; - private const short RL_ALERT = 21; - private const short RL_HANDSHAKE = 22; - private const short RL_APPLICATION_DATA = 23; - - /* - hello_request(0), client_hello(1), server_hello(2), - certificate(11), server_key_exchange (12), - certificate_request(13), server_hello_done(14), - certificate_verify(15), client_key_exchange(16), - finished(20), (255) - */ - - private const short HP_HELLO_REQUEST = 0; - private const short HP_CLIENT_HELLO = 1; - private const short HP_SERVER_HELLO = 2; - private const short HP_CERTIFICATE = 11; - private const short HP_SERVER_KEY_EXCHANGE = 12; - private const short HP_CERTIFICATE_REQUEST = 13; - private const short HP_SERVER_HELLO_DONE = 14; - private const short HP_CERTIFICATE_VERIFY = 15; - private const short HP_CLIENT_KEY_EXCHANGE = 16; - private const short HP_FINISHED = 20; - - /* - * Our Connection states - */ - - private const short CS_CLIENT_HELLO_SEND = 1; - private const short CS_SERVER_HELLO_RECEIVED = 2; - private const short CS_SERVER_CERTIFICATE_RECEIVED = 3; - private const short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4; - private const short CS_CERTIFICATE_REQUEST_RECEIVED = 5; - private const short CS_SERVER_HELLO_DONE_RECEIVED = 6; - private const short CS_CLIENT_KEY_EXCHANGE_SEND = 7; - private const short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 8; - private const short CS_CLIENT_FINISHED_SEND = 9; - private const short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 10; - private const short CS_DONE = 11; - - internal const short AP_close_notify = 0; - internal const short AP_unexpected_message = 10; - internal const short AP_bad_record_mac = 20; - internal const short AP_decryption_failed = 21; - internal const short AP_record_overflow = 22; - internal const short AP_decompression_failure = 30; - internal const short AP_handshake_failure = 40; - internal const short AP_bad_certificate = 42; - internal const short AP_unsupported_certificate = 43; - internal const short AP_certificate_revoked = 44; - internal const short AP_certificate_expired = 45; - internal const short AP_certificate_unknown = 46; - internal const short AP_illegal_parameter = 47; - internal const short AP_unknown_ca = 48; - internal const short AP_access_denied = 49; - internal const short AP_decode_error = 50; - internal const short AP_decrypt_error = 51; - internal const short AP_export_restriction = 60; - internal const short AP_protocol_version = 70; - internal const short AP_insufficient_security = 71; - internal const short AP_internal_error = 80; - internal const short AP_user_canceled = 90; - internal const short AP_no_renegotiation = 100; - - internal const short AL_warning = 1; - internal const short AL_fatal = 2; - - private static readonly byte[] emptybuf = new byte[0]; - - private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack"; - - /* - * Queues for data from some protocols. - */ - - private ByteQueue applicationDataQueue = new ByteQueue(); - private ByteQueue changeCipherSpecQueue = new ByteQueue(); - private ByteQueue alertQueue = new ByteQueue(); - private ByteQueue handshakeQueue = new ByteQueue(); - - /* - * The Record Stream we use - */ - private RecordStream rs; - - private SecureRandom random; - - /* - * The public rsa-key of the server. - */ - private RsaKeyParameters serverRsaKey = null; - - private TlsInputStream tlsInputStream = null; - private TlsOuputStream tlsOutputStream = null; - - private bool closed = false; - private bool failedWithError = false; - private bool appDataReady = false; - - private byte[] clientRandom; - private byte[] serverRandom; - private byte[] ms; - - private TlsCipherSuite chosenCipherSuite = null; - - private BigInteger Yc; - private byte[] pms; - - private ICertificateVerifyer verifyer = null; - - /* - * Both streams can be the same object - */ - public TlsProtocolHandler( - Stream inStr, - Stream outStr) - { - /* - * We use a threaded seed generator to generate a good random - * seed. If the user has a better random seed, he should use - * the constructor with a SecureRandom. - * - * Hopefully, 20 bytes in fast mode are good enough. - */ - byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true); - - this.random = new SecureRandom(seed); - this.rs = new RecordStream(this, inStr, outStr); - } - - public TlsProtocolHandler( - Stream inStr, - Stream outStr, - SecureRandom sr) - { - this.random = sr; - this.rs = new RecordStream(this, inStr, outStr); - } - - private short connection_state; - - internal void ProcessData( - short protocol, - byte[] buf, - int offset, - int len) - { - /* - * Have a look at the protocol type, and add it to the correct queue. - */ - switch (protocol) - { - case RL_CHANGE_CIPHER_SPEC: - changeCipherSpecQueue.AddData(buf, offset, len); - processChangeCipherSpec(); - break; - case RL_ALERT: - alertQueue.AddData(buf, offset, len); - processAlert(); - break; - case RL_HANDSHAKE: - handshakeQueue.AddData(buf, offset, len); - processHandshake(); - break; - case RL_APPLICATION_DATA: - if (!appDataReady) - { - this.FailWithError(AL_fatal, AP_unexpected_message); - } - applicationDataQueue.AddData(buf, offset, len); - processApplicationData(); - break; - default: - /* - * Uh, we don't know this protocol. - * - * RFC2246 defines on page 13, that we should ignore this. - */ - break; - } - } - - private void processHandshake() - { - bool read; - do - { - read = false; - - /* - * We need the first 4 bytes, they contain type and length of - * the message. - */ - if (handshakeQueue.Available >= 4) - { - byte[] beginning = new byte[4]; - handshakeQueue.Read(beginning, 0, 4, 0); - MemoryStream bis = new MemoryStream(beginning, false); - short type = TlsUtilities.ReadUint8(bis); - int len = TlsUtilities.ReadUint24(bis); - - /* - * Check if we have enough bytes in the buffer to read - * the full message. - */ - if (handshakeQueue.Available >= (len + 4)) - { - /* - * Read the message. - */ - byte[] buf = new byte[len]; - handshakeQueue.Read(buf, 0, len, 4); - handshakeQueue.RemoveData(len + 4); - - /* - * If it is not a finished message, update our hashes - * we prepare for the finish message. - */ - if (type != HP_FINISHED) - { - rs.hash1.BlockUpdate(beginning, 0, 4); - rs.hash2.BlockUpdate(beginning, 0, 4); - rs.hash1.BlockUpdate(buf, 0, len); - rs.hash2.BlockUpdate(buf, 0, len); - } - - /* - * Now, parse the message. - */ - MemoryStream inStr = new MemoryStream(buf, false); - - /* - * Check the type. - */ - switch (type) - { - case HP_CERTIFICATE: - switch (connection_state) - { - case CS_SERVER_HELLO_RECEIVED: - /* - * Parse the certificates. - */ - Certificate cert = Certificate.Parse(inStr); - AssertEmpty(inStr); - - /* - * Verify them. - */ - if (!this.verifyer.IsValid(cert.GetCerts())) - { - this.FailWithError(AL_fatal, AP_user_canceled); - } - - /* - * We only support RSA certificates. Lets hope - * this is one. - */ - RsaPublicKeyStructure rsaKey = null; - try - { - rsaKey = RsaPublicKeyStructure.GetInstance( - cert.certs[0].TbsCertificate.SubjectPublicKeyInfo.GetPublicKey()); - } - catch (Exception) - { - /* - * Sorry, we have to fail ;-( - */ - this.FailWithError(AL_fatal, AP_unsupported_certificate); - } - - /* - * Parse the servers public RSA key. - */ - this.serverRsaKey = new RsaKeyParameters( - false, - rsaKey.Modulus, - rsaKey.PublicExponent); - - connection_state = CS_SERVER_CERTIFICATE_RECEIVED; - read = true; - break; - default: - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - } - break; - case HP_FINISHED: - switch (connection_state) - { - case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED: - /* - * Read the checksum from the finished message, - * it has always 12 bytes. - */ - byte[] receivedChecksum = new byte[12]; - TlsUtilities.ReadFully(receivedChecksum, inStr); - AssertEmpty(inStr); - - /* - * Calculate our own checksum. - */ - byte[] checksum = new byte[12]; - byte[] md5andsha1 = new byte[16 + 20]; - rs.hash2.DoFinal(md5andsha1, 0); - TlsUtilities.PRF(this.ms, TlsUtilities.ToByteArray("server finished"), md5andsha1, checksum); - - /* - * Compare both checksums. - */ - for (int i = 0; i < receivedChecksum.Length; i++) - { - if (receivedChecksum[i] != checksum[i]) - { - /* - * Wrong checksum in the finished message. - */ - this.FailWithError(AL_fatal, AP_handshake_failure); - } - } - - connection_state = CS_DONE; - - /* - * We are now ready to receive application data. - */ - this.appDataReady = true; - read = true; - break; - default: - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - } - break; - case HP_SERVER_HELLO: - switch (connection_state) - { - case CS_CLIENT_HELLO_SEND: - /* - * Read the server hello message - */ - TlsUtilities.CheckVersion(inStr, this); - - /* - * Read the server random - */ - this.serverRandom = new byte[32]; - TlsUtilities.ReadFully(this.serverRandom, inStr); - - /* - * Currently, we don't support session ids - */ - short sessionIdLength = TlsUtilities.ReadUint8(inStr); - byte[] sessionId = new byte[sessionIdLength]; - TlsUtilities.ReadFully(sessionId, inStr); - - /* - * Find out which ciphersuite the server has - * chosen. If we don't support this ciphersuite, - * the TlsCipherSuiteManager will throw an - * exception. - */ - this.chosenCipherSuite = TlsCipherSuiteManager.GetCipherSuite( - TlsUtilities.ReadUint16(inStr), this); - - /* - * We support only the null compression which - * means no compression. - */ - short compressionMethod = TlsUtilities.ReadUint8(inStr); - if (compressionMethod != 0) - { - this.FailWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_illegal_parameter); - } - AssertEmpty(inStr); - - connection_state = CS_SERVER_HELLO_RECEIVED; - read = true; - break; - default: - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - } - break; - case HP_SERVER_HELLO_DONE: - switch (connection_state) - { - case CS_SERVER_CERTIFICATE_RECEIVED: - case CS_SERVER_KEY_EXCHANGE_RECEIVED: - case CS_CERTIFICATE_REQUEST_RECEIVED: - - // NB: Original code used case label fall-through - if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED) - { - /* - * There was no server key exchange message, check - * that we are doing RSA key exchange. - */ - if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_RSA) - { - this.FailWithError(AL_fatal, AP_unexpected_message); - } - } - - AssertEmpty(inStr); - bool isCertReq = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED); - connection_state = CS_SERVER_HELLO_DONE_RECEIVED; - - if (isCertReq) - { - sendClientCertificate(); - } - - /* - * Send the client key exchange message, depending - * on the key exchange we are using in our - * ciphersuite. - */ - short ke = this.chosenCipherSuite.KeyExchangeAlgorithm; - - switch (ke) - { - case TlsCipherSuite.KE_RSA: - /* - * We are doing RSA key exchange. We will - * choose a pre master secret and send it - * rsa encrypted to the server. - * - * Prepare pre master secret. - */ - pms = new byte[48]; - pms[0] = 3; - pms[1] = 1; - random.NextBytes(pms, 2, 46); - - /* - * Encode the pms and send it to the server. - * - * Prepare an Pkcs1Encoding with good random - * padding. - */ - RsaBlindedEngine rsa = new RsaBlindedEngine(); - Pkcs1Encoding encoding = new Pkcs1Encoding(rsa); - encoding.Init(true, new ParametersWithRandom(this.serverRsaKey, this.random)); - byte[] encrypted = null; - try - { - encrypted = encoding.ProcessBlock(pms, 0, pms.Length); - } - catch (InvalidCipherTextException) - { - /* - * This should never happen, only during decryption. - */ - this.FailWithError(AL_fatal, AP_internal_error); - } - - /* - * Send the encrypted pms. - */ - MemoryStream bos = new MemoryStream(); - TlsUtilities.WriteUint8(HP_CLIENT_KEY_EXCHANGE, bos); - TlsUtilities.WriteUint24(encrypted.Length + 2, bos); - TlsUtilities.WriteUint16(encrypted.Length, bos); - bos.Write(encrypted, 0, encrypted.Length); - byte[] message = bos.ToArray(); - - rs.WriteMessage((short)RL_HANDSHAKE, message, 0, message.Length); - break; - case TlsCipherSuite.KE_DHE_RSA: - /* - * Send the Client Key Exchange message for - * DHE key exchange. - */ - byte[] YcByte = this.Yc.ToByteArray(); - MemoryStream DHbos = new MemoryStream(); - TlsUtilities.WriteUint8(HP_CLIENT_KEY_EXCHANGE, DHbos); - TlsUtilities.WriteUint24(YcByte.Length + 2, DHbos); - TlsUtilities.WriteUint16(YcByte.Length, DHbos); - DHbos.Write(YcByte, 0, YcByte.Length); - byte[] DHmessage = DHbos.ToArray(); - - rs.WriteMessage((short)RL_HANDSHAKE, DHmessage, 0, DHmessage.Length); - - break; - default: - /* - * Problem during handshake, we don't know - * how to handle this key exchange method. - */ - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - - } - - connection_state = CS_CLIENT_KEY_EXCHANGE_SEND; - - /* - * Now, we send change cipher state - */ - byte[] cmessage = new byte[1]; - cmessage[0] = 1; - rs.WriteMessage((short)RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.Length); - - connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND; - - /* - * Calculate the ms - */ - this.ms = new byte[48]; - byte[] randBytes = new byte[clientRandom.Length + serverRandom.Length]; - Array.Copy(clientRandom, 0, randBytes, 0, clientRandom.Length); - Array.Copy(serverRandom, 0, randBytes, clientRandom.Length, serverRandom.Length); - TlsUtilities.PRF(pms, TlsUtilities.ToByteArray("master secret"), randBytes, this.ms); - - /* - * Initialize our cipher suite - */ - rs.writeSuite = this.chosenCipherSuite; - rs.writeSuite.Init(this.ms, clientRandom, serverRandom); - - /* - * Send our finished message. - */ - byte[] checksum = new byte[12]; - byte[] md5andsha1 = new byte[16 + 20]; - rs.hash1.DoFinal(md5andsha1, 0); - TlsUtilities.PRF(this.ms, TlsUtilities.ToByteArray("client finished"), md5andsha1, checksum); - - MemoryStream bos2 = new MemoryStream(); - TlsUtilities.WriteUint8(HP_FINISHED, bos2); - TlsUtilities.WriteUint24(12, bos2); - bos2.Write(checksum, 0, checksum.Length); - byte[] message2 = bos2.ToArray(); - - rs.WriteMessage((short)RL_HANDSHAKE, message2, 0, message2.Length); - - this.connection_state = CS_CLIENT_FINISHED_SEND; - read = true; - break; - default: - this.FailWithError(AL_fatal, AP_handshake_failure); - break; - } - break; - case HP_SERVER_KEY_EXCHANGE: - switch (connection_state) - { - case CS_SERVER_CERTIFICATE_RECEIVED: - /* - * Check that we are doing DHE key exchange - */ - if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_DHE_RSA) - { - this.FailWithError(AL_fatal, AP_unexpected_message); - } - - /* - * Parse the Structure - */ - int pLength = TlsUtilities.ReadUint16(inStr); - byte[] pByte = new byte[pLength]; - TlsUtilities.ReadFully(pByte, inStr); - - int gLength = TlsUtilities.ReadUint16(inStr); - byte[] gByte = new byte[gLength]; - TlsUtilities.ReadFully(gByte, inStr); - - int YsLength = TlsUtilities.ReadUint16(inStr); - byte[] YsByte = new byte[YsLength]; - TlsUtilities.ReadFully(YsByte, inStr); - - int sigLength = TlsUtilities.ReadUint16(inStr); - byte[] sigByte = new byte[sigLength]; - TlsUtilities.ReadFully(sigByte, inStr); - - AssertEmpty(inStr); - - /* - * Verify the Signature. - * - * First, calculate the hash. - */ - CombinedHash sigDigest = new CombinedHash(); - MemoryStream signedData = new MemoryStream(); - TlsUtilities.WriteUint16(pLength, signedData); - signedData.Write(pByte, 0, pByte.Length); - TlsUtilities.WriteUint16(gLength, signedData); - signedData.Write(gByte, 0, gByte.Length); - TlsUtilities.WriteUint16(YsLength, signedData); - signedData.Write(YsByte, 0, YsByte.Length); - byte[] signed = signedData.ToArray(); - - sigDigest.BlockUpdate(this.clientRandom, 0, this.clientRandom.Length); - sigDigest.BlockUpdate(this.serverRandom, 0, this.serverRandom.Length); - sigDigest.BlockUpdate(signed, 0, signed.Length); - byte[] hash = new byte[sigDigest.GetDigestSize()]; - sigDigest.DoFinal(hash, 0); - - /* - * Now, do the RSA operation - */ - RsaBlindedEngine rsa = new RsaBlindedEngine(); - Pkcs1Encoding encoding = new Pkcs1Encoding(rsa); - encoding.Init(false, this.serverRsaKey); - - /* - * The data which was signed - */ - byte[] sigHash = null; - - try - { - sigHash = encoding.ProcessBlock(sigByte, 0, sigByte.Length); - } - catch (InvalidCipherTextException) - { - this.FailWithError(AL_fatal, AP_bad_certificate); - } - - /* - * Check if the data which was signed is equal to - * the hash we calculated. - */ - if (sigHash.Length != hash.Length) - { - this.FailWithError(AL_fatal, AP_bad_certificate); - } - - for (int i = 0; i < sigHash.Length; i++) - { - if (sigHash[i] != hash[i]) - { - this.FailWithError(AL_fatal, AP_bad_certificate); - } - } - - /* - * OK, Signature was correct. - * - * Do the DH calculation. - */ - BigInteger p = new BigInteger(1, pByte); - BigInteger g = new BigInteger(1, gByte); - BigInteger Ys = new BigInteger(1, YsByte); - BigInteger x = new BigInteger(p.BitLength - 1, this.random); - Yc = g.ModPow(x, p); - this.pms = Ys.ModPow(x, p).ToByteArrayUnsigned(); - - this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED; - read = true; - break; - default: - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - } - break; - case HP_CERTIFICATE_REQUEST: - switch (connection_state) - { - case CS_SERVER_CERTIFICATE_RECEIVED: - case CS_SERVER_KEY_EXCHANGE_RECEIVED: - - // NB: Original code used case label fall-through - if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED) - { - /* - * There was no server key exchange message, check - * that we are doing RSA key exchange. - */ - if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_RSA) - { - this.FailWithError(AL_fatal, AP_unexpected_message); - } - } - - int typesLength = TlsUtilities.ReadUint8(inStr); - byte[] types = new byte[typesLength]; - TlsUtilities.ReadFully(types, inStr); - - int authsLength = TlsUtilities.ReadUint16(inStr); - byte[] auths = new byte[authsLength]; - TlsUtilities.ReadFully(auths, inStr); - - AssertEmpty(inStr); - - this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED; - read = true; - break; - default: - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - } - break; - case HP_HELLO_REQUEST: - case HP_CLIENT_KEY_EXCHANGE: - case HP_CERTIFICATE_VERIFY: - case HP_CLIENT_HELLO: - default: - // We do not support this! - this.FailWithError(AL_fatal, AP_unexpected_message); - break; - - } - - } - } - } - while (read); - - } - - private void processApplicationData() - { - /* - * There is nothing we need to do here. - * - * This function could be used for callbacks when application - * data arrives in the future. - */ - } - - private void processAlert() - { - while (alertQueue.Available >= 2) - { - /* - * An alert is always 2 bytes. Read the alert. - */ - byte[] tmp = new byte[2]; - alertQueue.Read(tmp, 0, 2, 0); - alertQueue.RemoveData(2); - short level = tmp[0]; - short description = tmp[1]; - if (level == AL_fatal) - { - /* - * This is a fatal error. - */ - this.failedWithError = true; - this.closed = true; - /* - * Now try to Close the stream, ignore errors. - */ - try - { - rs.Close(); - } - catch (Exception) - { - } - throw new IOException(TLS_ERROR_MESSAGE); - } - else - { - /* - * This is just a warning. - */ - if (description == AP_close_notify) - { - /* - * Close notify - */ - this.FailWithError(AL_warning, AP_close_notify); - } - /* - * If it is just a warning, we continue. - */ - } - } - - } - - /** - * This method is called, when a change cipher spec message is received. - * - * @throws IOException If the message has an invalid content or the - * handshake is not in the correct state. - */ - private void processChangeCipherSpec() - { - while (changeCipherSpecQueue.Available > 0) - { - /* - * A change cipher spec message is only one byte with the value 1. - */ - byte[] b = new byte[1]; - changeCipherSpecQueue.Read(b, 0, 1, 0); - changeCipherSpecQueue.RemoveData(1); - if (b[0] != 1) - { - /* - * This should never happen. - */ - this.FailWithError(AL_fatal, AP_unexpected_message); - - } - else - { - /* - * Check if we are in the correct connection state. - */ - if (this.connection_state == CS_CLIENT_FINISHED_SEND) - { - rs.readSuite = rs.writeSuite; - this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED; - } - else - { - /* - * We are not in the correct connection state. - */ - this.FailWithError(AL_fatal, AP_handshake_failure); - } - - } - } - } - - private void sendClientCertificate() - { - /* - * just write back the "no client certificate" message - * see also gnutls, auth_cert.c:643 (0B 00 00 03 00 00 00) - */ - MemoryStream bos = new MemoryStream(); - TlsUtilities.WriteUint8(HP_CERTIFICATE, bos); - TlsUtilities.WriteUint24(3, bos); - TlsUtilities.WriteUint24(0, bos); - byte[] message = bos.ToArray(); - - rs.WriteMessage((short)RL_HANDSHAKE, message, 0, message.Length); - } - - /// Connects to the remote system. - /// Will be used when a certificate is received to verify - /// that this certificate is accepted by the client. - /// If handshake was not successful - public virtual void Connect( - ICertificateVerifyer verifyer) - { - this.verifyer = verifyer; - - /* - * Send Client hello - * - * First, generate some random data. - */ - this.clientRandom = new byte[32]; - - /* - * TLS 1.0 requires a unix-timestamp in the first 4 bytes - */ - int t = (int)(DateTimeUtilities.CurrentUnixMs() / 1000L); - this.clientRandom[0] = (byte)(t >> 24); - this.clientRandom[1] = (byte)(t >> 16); - this.clientRandom[2] = (byte)(t >> 8); - this.clientRandom[3] = (byte)t; - - random.NextBytes(this.clientRandom, 4, 28); - - - MemoryStream outStr = new MemoryStream(); - TlsUtilities.WriteVersion(outStr); - outStr.Write(this.clientRandom, 0, this.clientRandom.Length); - - /* - * Length of Session id - */ - TlsUtilities.WriteUint8((short)0, outStr); - - /* - * Cipher suites - */ - TlsCipherSuiteManager.WriteCipherSuites(outStr); - - /* - * Compression methods, just the null method. - */ - byte[] compressionMethods = new byte[]{0x00}; - TlsUtilities.WriteUint8((short)compressionMethods.Length, outStr); - outStr.Write(compressionMethods,0, compressionMethods.Length); - - - MemoryStream bos = new MemoryStream(); - TlsUtilities.WriteUint8(HP_CLIENT_HELLO, bos); - TlsUtilities.WriteUint24((int) outStr.Length, bos); - byte[] outBytes = outStr.ToArray(); - bos.Write(outBytes, 0, outBytes.Length); - byte[] message = bos.ToArray(); - rs.WriteMessage(RL_HANDSHAKE, message, 0, message.Length); - connection_state = CS_CLIENT_HELLO_SEND; - - /* - * We will now read data, until we have completed the handshake. - */ - while (connection_state != CS_DONE) - { - rs.ReadData(); - } - - this.tlsInputStream = new TlsInputStream(this); - this.tlsOutputStream = new TlsOuputStream(this); - } - - /** - * Read data from the network. The method will return immed, if there is - * still some data left in the buffer, or block untill some application - * data has been read from the network. - * - * @param buf The buffer where the data will be copied to. - * @param offset The position where the data will be placed in the buffer. - * @param len The maximum number of bytes to read. - * @return The number of bytes read. - * @throws IOException If something goes wrong during reading data. - */ - internal int ReadApplicationData(byte[] buf, int offset, int len) - { - while (applicationDataQueue.Available == 0) - { - /* - * We need to read some data. - */ - if (this.failedWithError) - { - /* - * Something went terribly wrong, we should throw an IOException - */ - throw new IOException(TLS_ERROR_MESSAGE); - } - if (this.closed) - { - /* - * Connection has been closed, there is no more data to read. - */ - return 0; - } - - try - { - rs.ReadData(); - } - catch (IOException e) - { - if (!this.closed) - { - this.FailWithError(AL_fatal, AP_internal_error); - } - throw e; - } - catch (Exception e) - { - if (!this.closed) - { - this.FailWithError(AL_fatal, AP_internal_error); - } - throw e; - } - } - len = System.Math.Min(len, applicationDataQueue.Available); - applicationDataQueue.Read(buf, offset, len, 0); - applicationDataQueue.RemoveData(len); - return len; - } - - /** - * Send some application data to the remote system. - *

- * The method will handle fragmentation internally. - * - * @param buf The buffer with the data. - * @param offset The position in the buffer where the data is placed. - * @param len The length of the data. - * @throws IOException If something goes wrong during sending. - */ - internal void WriteData(byte[] buf, int offset, int len) - { - if (this.failedWithError) - { - throw new IOException(TLS_ERROR_MESSAGE); - } - if (this.closed) - { - throw new IOException("Sorry, connection has been closed, you cannot write more data"); - } - - /* - * Protect against known IV attack! - * - * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT - * YOU ARE DOING HERE. - */ - rs.WriteMessage(RL_APPLICATION_DATA, emptybuf, 0, 0); - - do - { - /* - * We are only allowed to write fragments up to 2^14 bytes. - */ - int toWrite = System.Math.Min(len, 1 << 14); - - try - { - rs.WriteMessage(RL_APPLICATION_DATA, buf, offset, toWrite); - } - catch (IOException e) - { - if (!closed) - { - this.FailWithError(AL_fatal, AP_internal_error); - } - throw e; - } - catch (Exception e) - { - if (!closed) - { - this.FailWithError(AL_fatal, AP_internal_error); - } - throw e; - } - - - offset += toWrite; - len -= toWrite; - } - while (len > 0); - - } - - [Obsolete("Use 'OutputStream' property instead")] - public TlsOuputStream TlsOuputStream - { - get { return this.tlsOutputStream; } - } - - ///

A Stream which can be used to send data. - public virtual Stream OutputStream - { - get { return this.tlsOutputStream; } - } - - [Obsolete("Use 'InputStream' property instead")] - public TlsInputStream TlsInputStream - { - get { return this.tlsInputStream; } - } - - /// A Stream which can be used to read data. - public virtual Stream InputStream - { - get { return this.tlsInputStream; } - } - - /** - * Terminate this connection whith an alert. - *

- * Can be used for normal closure too. - * - * @param alertLevel The level of the alert, an be AL_fatal or AL_warning. - * @param alertDescription The exact alert message. - * @throws IOException If alert was fatal. - */ - internal void FailWithError( - short alertLevel, - short alertDescription) - { - /* - * Check if the connection is still open. - */ - if (!closed) - { - /* - * Prepare the message - */ - byte[] error = new byte[2]; - error[0] = (byte)alertLevel; - error[1] = (byte)alertDescription; - this.closed = true; - - if (alertLevel == AL_fatal) - { - /* - * This is a fatal message. - */ - this.failedWithError = true; - } - rs.WriteMessage(RL_ALERT, error, 0, 2); - rs.Close(); - if (alertLevel == AL_fatal) - { - throw new IOException(TLS_ERROR_MESSAGE); - } - - } - else - { - throw new IOException(TLS_ERROR_MESSAGE); - } - } - - ///

Closes this connection - /// If something goes wrong during closing. - public virtual void Close() - { - if (!closed) - { - this.FailWithError((short)1, (short)0); - } - } - - /** - * Make sure the Stream is now empty. Fail otherwise. - * - * @param is The Stream to check. - * @throws IOException If is is not empty. - */ - internal void AssertEmpty( - MemoryStream inStr) - { -// if (inStr.available() > 0) - if (inStr.Position < inStr.Length) - { - this.FailWithError(AL_fatal, AP_decode_error); - } - } - - internal void Flush() - { - rs.Flush(); - } - } -} +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// An implementation of all high level protocols in TLS 1.0. + public class TlsProtocolHandler + { + private const short RL_CHANGE_CIPHER_SPEC = 20; + private const short RL_ALERT = 21; + private const short RL_HANDSHAKE = 22; + private const short RL_APPLICATION_DATA = 23; + + /* + hello_request(0), client_hello(1), server_hello(2), + certificate(11), server_key_exchange (12), + certificate_request(13), server_hello_done(14), + certificate_verify(15), client_key_exchange(16), + finished(20), (255) + */ + + private const short HP_HELLO_REQUEST = 0; + private const short HP_CLIENT_HELLO = 1; + private const short HP_SERVER_HELLO = 2; + private const short HP_CERTIFICATE = 11; + private const short HP_SERVER_KEY_EXCHANGE = 12; + private const short HP_CERTIFICATE_REQUEST = 13; + private const short HP_SERVER_HELLO_DONE = 14; + private const short HP_CERTIFICATE_VERIFY = 15; + private const short HP_CLIENT_KEY_EXCHANGE = 16; + private const short HP_FINISHED = 20; + + /* + * Our Connection states + */ + + private const short CS_CLIENT_HELLO_SEND = 1; + private const short CS_SERVER_HELLO_RECEIVED = 2; + private const short CS_SERVER_CERTIFICATE_RECEIVED = 3; + private const short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4; + private const short CS_CERTIFICATE_REQUEST_RECEIVED = 5; + private const short CS_SERVER_HELLO_DONE_RECEIVED = 6; + private const short CS_CLIENT_KEY_EXCHANGE_SEND = 7; + private const short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 8; + private const short CS_CLIENT_FINISHED_SEND = 9; + private const short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 10; + private const short CS_DONE = 11; + + internal const short AP_close_notify = 0; + internal const short AP_unexpected_message = 10; + internal const short AP_bad_record_mac = 20; + internal const short AP_decryption_failed = 21; + internal const short AP_record_overflow = 22; + internal const short AP_decompression_failure = 30; + internal const short AP_handshake_failure = 40; + internal const short AP_bad_certificate = 42; + internal const short AP_unsupported_certificate = 43; + internal const short AP_certificate_revoked = 44; + internal const short AP_certificate_expired = 45; + internal const short AP_certificate_unknown = 46; + internal const short AP_illegal_parameter = 47; + internal const short AP_unknown_ca = 48; + internal const short AP_access_denied = 49; + internal const short AP_decode_error = 50; + internal const short AP_decrypt_error = 51; + internal const short AP_export_restriction = 60; + internal const short AP_protocol_version = 70; + internal const short AP_insufficient_security = 71; + internal const short AP_internal_error = 80; + internal const short AP_user_canceled = 90; + internal const short AP_no_renegotiation = 100; + + internal const short AL_warning = 1; + internal const short AL_fatal = 2; + + private static readonly byte[] emptybuf = new byte[0]; + + private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack"; + + /* + * Queues for data from some protocols. + */ + + private ByteQueue applicationDataQueue = new ByteQueue(); + private ByteQueue changeCipherSpecQueue = new ByteQueue(); + private ByteQueue alertQueue = new ByteQueue(); + private ByteQueue handshakeQueue = new ByteQueue(); + + /* + * The Record Stream we use + */ + private RecordStream rs; + + private SecureRandom random; + + /* + * The public key of the server. + */ + private AsymmetricKeyParameter serverPublicKey = null; + + private TlsInputStream tlsInputStream = null; + private TlsOuputStream tlsOutputStream = null; + + private bool closed = false; + private bool failedWithError = false; + private bool appDataReady = false; + + private byte[] clientRandom; + private byte[] serverRandom; + private byte[] ms; + + private TlsCipherSuite chosenCipherSuite = null; + + private BigInteger Yc; + private byte[] pms; + + private ICertificateVerifyer verifyer = null; + + /* + * Both streams can be the same object + */ + public TlsProtocolHandler( + Stream inStr, + Stream outStr) + { + /* + * We use a threaded seed generator to generate a good random + * seed. If the user has a better random seed, he should use + * the constructor with a SecureRandom. + * + * Hopefully, 20 bytes in fast mode are good enough. + */ + byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true); + + this.random = new SecureRandom(seed); + this.rs = new RecordStream(this, inStr, outStr); + } + + public TlsProtocolHandler( + Stream inStr, + Stream outStr, + SecureRandom sr) + { + this.random = sr; + this.rs = new RecordStream(this, inStr, outStr); + } + + private short connection_state; + + internal void ProcessData( + short protocol, + byte[] buf, + int offset, + int len) + { + /* + * Have a look at the protocol type, and add it to the correct queue. + */ + switch (protocol) + { + case RL_CHANGE_CIPHER_SPEC: + changeCipherSpecQueue.AddData(buf, offset, len); + processChangeCipherSpec(); + break; + case RL_ALERT: + alertQueue.AddData(buf, offset, len); + processAlert(); + break; + case RL_HANDSHAKE: + handshakeQueue.AddData(buf, offset, len); + processHandshake(); + break; + case RL_APPLICATION_DATA: + if (!appDataReady) + { + this.FailWithError(AL_fatal, AP_unexpected_message); + } + applicationDataQueue.AddData(buf, offset, len); + processApplicationData(); + break; + default: + /* + * Uh, we don't know this protocol. + * + * RFC2246 defines on page 13, that we should ignore this. + */ + break; + } + } + + private void processHandshake() + { + bool read; + do + { + read = false; + + /* + * We need the first 4 bytes, they contain type and length of + * the message. + */ + if (handshakeQueue.Available >= 4) + { + byte[] beginning = new byte[4]; + handshakeQueue.Read(beginning, 0, 4, 0); + MemoryStream bis = new MemoryStream(beginning, false); + short type = TlsUtilities.ReadUint8(bis); + int len = TlsUtilities.ReadUint24(bis); + + /* + * Check if we have enough bytes in the buffer to read + * the full message. + */ + if (handshakeQueue.Available >= (len + 4)) + { + /* + * Read the message. + */ + byte[] buf = new byte[len]; + handshakeQueue.Read(buf, 0, len, 4); + handshakeQueue.RemoveData(len + 4); + + /* + * If it is not a finished message, update our hashes + * we prepare for the finish message. + */ + if (type != HP_FINISHED) + { + rs.hash1.BlockUpdate(beginning, 0, 4); + rs.hash2.BlockUpdate(beginning, 0, 4); + rs.hash1.BlockUpdate(buf, 0, len); + rs.hash2.BlockUpdate(buf, 0, len); + } + + /* + * Now, parse the message. + */ + MemoryStream inStr = new MemoryStream(buf, false); + + /* + * Check the type. + */ + switch (type) + { + case HP_CERTIFICATE: + { + switch (connection_state) + { + case CS_SERVER_HELLO_RECEIVED: + { + /* + * Parse the certificates. + */ + Certificate cert = Certificate.Parse(inStr); + AssertEmpty(inStr); + + X509CertificateStructure x509Cert = cert.certs[0]; + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + + try + { + this.serverPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception e) + { + this.FailWithError(AL_fatal, AP_unsupported_certificate); + } + + // Sanity check the PublicKeyFactory + if (this.serverPublicKey.IsPrivate) + { + this.FailWithError(AL_fatal, AP_internal_error); + } + + /* + * Perform various checks per RFC2246 7.4.2 + * TODO "Unless otherwise specified, the signing algorithm for the certificate + * must be the same as the algorithm for the certificate key." + */ + switch (this.chosenCipherSuite.KeyExchangeAlgorithm) + { + case TlsCipherSuite.KE_RSA: + if (!(this.serverPublicKey is RsaKeyParameters)) + { + this.FailWithError(AL_fatal, AP_certificate_unknown); + } + validateKeyUsage(x509Cert, KeyUsage.KeyEncipherment); + break; + case TlsCipherSuite.KE_DHE_RSA: + if (!(this.serverPublicKey is RsaKeyParameters)) + { + this.FailWithError(AL_fatal, AP_certificate_unknown); + } + validateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + break; + case TlsCipherSuite.KE_DHE_DSS: + if (!(this.serverPublicKey is DsaPublicKeyParameters)) + { + this.FailWithError(AL_fatal, AP_certificate_unknown); + } + break; + default: + this.FailWithError(AL_fatal, AP_unsupported_certificate); + break; + } + + /* + * Verify them. + */ + if (!this.verifyer.IsValid(cert.GetCerts())) + { + this.FailWithError(AL_fatal, AP_user_canceled); + } + + break; + } + default: + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + + connection_state = CS_SERVER_CERTIFICATE_RECEIVED; + read = true; + break; + } + case HP_FINISHED: + switch (connection_state) + { + case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED: + /* + * Read the checksum from the finished message, + * it has always 12 bytes. + */ + byte[] receivedChecksum = new byte[12]; + TlsUtilities.ReadFully(receivedChecksum, inStr); + AssertEmpty(inStr); + + /* + * Calculate our own checksum. + */ + byte[] checksum = new byte[12]; + byte[] md5andsha1 = new byte[16 + 20]; + rs.hash2.DoFinal(md5andsha1, 0); + TlsUtilities.PRF(this.ms, TlsUtilities.ToByteArray("server finished"), md5andsha1, checksum); + + /* + * Compare both checksums. + */ + for (int i = 0; i < receivedChecksum.Length; i++) + { + if (receivedChecksum[i] != checksum[i]) + { + /* + * Wrong checksum in the finished message. + */ + this.FailWithError(AL_fatal, AP_handshake_failure); + } + } + + connection_state = CS_DONE; + + /* + * We are now ready to receive application data. + */ + this.appDataReady = true; + read = true; + break; + default: + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + break; + case HP_SERVER_HELLO: + switch (connection_state) + { + case CS_CLIENT_HELLO_SEND: + /* + * Read the server hello message + */ + TlsUtilities.CheckVersion(inStr, this); + + /* + * Read the server random + */ + this.serverRandom = new byte[32]; + TlsUtilities.ReadFully(this.serverRandom, inStr); + + /* + * Currently, we don't support session ids + */ + short sessionIdLength = TlsUtilities.ReadUint8(inStr); + byte[] sessionId = new byte[sessionIdLength]; + TlsUtilities.ReadFully(sessionId, inStr); + + /* + * Find out which ciphersuite the server has + * chosen. If we don't support this ciphersuite, + * the TlsCipherSuiteManager will throw an + * exception. + */ + this.chosenCipherSuite = TlsCipherSuiteManager.GetCipherSuite( + TlsUtilities.ReadUint16(inStr), this); + + /* + * We support only the null compression which + * means no compression. + */ + short compressionMethod = TlsUtilities.ReadUint8(inStr); + if (compressionMethod != 0) + { + this.FailWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_illegal_parameter); + } + AssertEmpty(inStr); + + connection_state = CS_SERVER_HELLO_RECEIVED; + read = true; + break; + default: + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + break; + case HP_SERVER_HELLO_DONE: + switch (connection_state) + { + case CS_SERVER_CERTIFICATE_RECEIVED: + case CS_SERVER_KEY_EXCHANGE_RECEIVED: + case CS_CERTIFICATE_REQUEST_RECEIVED: + + // NB: Original code used case label fall-through + if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED) + { + /* + * There was no server key exchange message, check + * that we are doing RSA key exchange. + */ + if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_RSA) + { + this.FailWithError(AL_fatal, AP_unexpected_message); + } + } + + AssertEmpty(inStr); + bool isCertReq = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED); + connection_state = CS_SERVER_HELLO_DONE_RECEIVED; + + if (isCertReq) + { + sendClientCertificate(); + } + + /* + * Send the client key exchange message, depending + * on the key exchange we are using in our + * ciphersuite. + */ + switch (this.chosenCipherSuite.KeyExchangeAlgorithm) + { + case TlsCipherSuite.KE_RSA: + { + /* + * We are doing RSA key exchange. We will + * choose a pre master secret and send it + * rsa encrypted to the server. + * + * Prepare pre master secret. + */ + pms = new byte[48]; + pms[0] = 3; + pms[1] = 1; + random.NextBytes(pms, 2, 46); + + /* + * Encode the pms and send it to the server. + * + * Prepare an Pkcs1Encoding with good random + * padding. + */ + RsaBlindedEngine rsa = new RsaBlindedEngine(); + Pkcs1Encoding encoding = new Pkcs1Encoding(rsa); + encoding.Init(true, new ParametersWithRandom(this.serverPublicKey, this.random)); + byte[] encrypted = null; + try + { + encrypted = encoding.ProcessBlock(pms, 0, pms.Length); + } + catch (InvalidCipherTextException) + { + /* + * This should never happen, only during decryption. + */ + this.FailWithError(AL_fatal, AP_internal_error); + } + + /* + * Send the encrypted pms. + */ + sendClientKeyExchange(encrypted); + break; + } + case TlsCipherSuite.KE_DHE_DSS: + case TlsCipherSuite.KE_DHE_RSA: + { + /* + * Send the Client Key Exchange message for + * DHE key exchange. + */ + byte[] YcByte = BigIntegers.AsUnsignedByteArray(this.Yc); + + sendClientKeyExchange(YcByte); + + break; + } + default: + /* + * Problem during handshake, we don't know + * how to handle this key exchange method. + */ + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + + } + + connection_state = CS_CLIENT_KEY_EXCHANGE_SEND; + + /* + * Now, we send change cipher state + */ + byte[] cmessage = new byte[1]; + cmessage[0] = 1; + rs.WriteMessage((short)RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.Length); + + connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND; + + /* + * Calculate the ms + */ + this.ms = new byte[48]; + byte[] randBytes = new byte[clientRandom.Length + serverRandom.Length]; + Array.Copy(clientRandom, 0, randBytes, 0, clientRandom.Length); + Array.Copy(serverRandom, 0, randBytes, clientRandom.Length, serverRandom.Length); + TlsUtilities.PRF(pms, TlsUtilities.ToByteArray("master secret"), randBytes, this.ms); + + /* + * Initialize our cipher suite + */ + rs.writeSuite = this.chosenCipherSuite; + rs.writeSuite.Init(this.ms, clientRandom, serverRandom); + + /* + * Send our finished message. + */ + byte[] checksum = new byte[12]; + byte[] md5andsha1 = new byte[16 + 20]; + rs.hash1.DoFinal(md5andsha1, 0); + TlsUtilities.PRF(this.ms, TlsUtilities.ToByteArray("client finished"), md5andsha1, checksum); + + MemoryStream bos2 = new MemoryStream(); + TlsUtilities.WriteUint8(HP_FINISHED, bos2); + TlsUtilities.WriteUint24(12, bos2); + bos2.Write(checksum, 0, checksum.Length); + byte[] message2 = bos2.ToArray(); + + rs.WriteMessage((short)RL_HANDSHAKE, message2, 0, message2.Length); + + this.connection_state = CS_CLIENT_FINISHED_SEND; + read = true; + break; + default: + this.FailWithError(AL_fatal, AP_handshake_failure); + break; + } + break; + case HP_SERVER_KEY_EXCHANGE: + { + switch (connection_state) + { + case CS_SERVER_CERTIFICATE_RECEIVED: + { + /* + * Check that we are doing DHE key exchange + */ + switch (this.chosenCipherSuite.KeyExchangeAlgorithm) + { + case TlsCipherSuite.KE_DHE_RSA: + { + processDHEKeyExchange(inStr, new TlsRsaSigner()); + break; + } + case TlsCipherSuite.KE_DHE_DSS: + { + processDHEKeyExchange(inStr, new TlsDssSigner()); + break; + } + default: + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + break; + } + default: + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + + this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED; + read = true; + break; + } + case HP_CERTIFICATE_REQUEST: + switch (connection_state) + { + case CS_SERVER_CERTIFICATE_RECEIVED: + case CS_SERVER_KEY_EXCHANGE_RECEIVED: + { + // NB: Original code used case label fall-through + if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED) + { + /* + * There was no server key exchange message, check + * that we are doing RSA key exchange. + */ + if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_RSA) + { + this.FailWithError(AL_fatal, AP_unexpected_message); + } + } + + int typesLength = TlsUtilities.ReadUint8(inStr); + byte[] types = new byte[typesLength]; + TlsUtilities.ReadFully(types, inStr); + + int authsLength = TlsUtilities.ReadUint16(inStr); + byte[] auths = new byte[authsLength]; + TlsUtilities.ReadFully(auths, inStr); + + AssertEmpty(inStr); + break; + } + default: + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + + this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED; + read = true; + break; + case HP_HELLO_REQUEST: + case HP_CLIENT_KEY_EXCHANGE: + case HP_CERTIFICATE_VERIFY: + case HP_CLIENT_HELLO: + default: + // We do not support this! + this.FailWithError(AL_fatal, AP_unexpected_message); + break; + } + } + } + } + while (read); + } + + private void processApplicationData() + { + /* + * There is nothing we need to do here. + * + * This function could be used for callbacks when application + * data arrives in the future. + */ + } + + private void processAlert() + { + while (alertQueue.Available >= 2) + { + /* + * An alert is always 2 bytes. Read the alert. + */ + byte[] tmp = new byte[2]; + alertQueue.Read(tmp, 0, 2, 0); + alertQueue.RemoveData(2); + short level = tmp[0]; + short description = tmp[1]; + if (level == AL_fatal) + { + /* + * This is a fatal error. + */ + this.failedWithError = true; + this.closed = true; + /* + * Now try to Close the stream, ignore errors. + */ + try + { + rs.Close(); + } + catch (Exception) + { + } + throw new IOException(TLS_ERROR_MESSAGE); + } + else + { + /* + * This is just a warning. + */ + if (description == AP_close_notify) + { + /* + * Close notify + */ + this.FailWithError(AL_warning, AP_close_notify); + } + /* + * If it is just a warning, we continue. + */ + } + } + } + + /** + * This method is called, when a change cipher spec message is received. + * + * @throws IOException If the message has an invalid content or the + * handshake is not in the correct state. + */ + private void processChangeCipherSpec() + { + while (changeCipherSpecQueue.Available > 0) + { + /* + * A change cipher spec message is only one byte with the value 1. + */ + byte[] b = new byte[1]; + changeCipherSpecQueue.Read(b, 0, 1, 0); + changeCipherSpecQueue.RemoveData(1); + if (b[0] != 1) + { + /* + * This should never happen. + */ + this.FailWithError(AL_fatal, AP_unexpected_message); + + } + else + { + /* + * Check if we are in the correct connection state. + */ + if (this.connection_state == CS_CLIENT_FINISHED_SEND) + { + rs.readSuite = rs.writeSuite; + this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED; + } + else + { + /* + * We are not in the correct connection state. + */ + this.FailWithError(AL_fatal, AP_handshake_failure); + } + + } + } + } + + private void processDHEKeyExchange( + MemoryStream inStr, + ISigner signer) + { + Stream sigIn = inStr; + if (signer != null) + { + signer.Init(false, this.serverPublicKey); + signer.BlockUpdate(this.clientRandom, 0, this.clientRandom.Length); + signer.BlockUpdate(this.serverRandom, 0, this.serverRandom.Length); + + sigIn = new SignerStream(inStr, signer, null); + } + + /* + * Parse the Structure + */ + int pLength = TlsUtilities.ReadUint16(sigIn); + byte[] pByte = new byte[pLength]; + TlsUtilities.ReadFully(pByte, sigIn); + + int gLength = TlsUtilities.ReadUint16(sigIn); + byte[] gByte = new byte[gLength]; + TlsUtilities.ReadFully(gByte, sigIn); + + int YsLength = TlsUtilities.ReadUint16(sigIn); + byte[] YsByte = new byte[YsLength]; + TlsUtilities.ReadFully(YsByte, sigIn); + + if (signer != null) + { + int sigLength = TlsUtilities.ReadUint16(inStr); + byte[] sigByte = new byte[sigLength]; + TlsUtilities.ReadFully(sigByte, inStr); + + /* + * Verify the Signature. + */ + if (!signer.VerifySignature(sigByte)) + { + this.FailWithError(AL_fatal, AP_bad_certificate); + } + } + + this.AssertEmpty(inStr); + + /* + * Do the DH calculation. + */ + BigInteger p = new BigInteger(1, pByte); + BigInteger g = new BigInteger(1, gByte); + BigInteger Ys = new BigInteger(1, YsByte); + + /* + * Check the DH parameter values + */ + if (!p.IsProbablePrime(10)) + { + this.FailWithError(AL_fatal, AP_illegal_parameter); + } + if (g.CompareTo(BigInteger.Two) < 0 || g.CompareTo(p.Subtract(BigInteger.Two)) > 0) + { + this.FailWithError(AL_fatal, AP_illegal_parameter); + } + // TODO For static DH public values, see additional checks in RFC 2631 2.1.5 + if (Ys.CompareTo(BigInteger.Two) < 0 || Ys.CompareTo(p.Subtract(BigInteger.One)) > 0) + { + this.FailWithError(AL_fatal, AP_illegal_parameter); + } + + /* + * Diffie-Hellman basic key agreement + */ + DHParameters dhParams = new DHParameters(p, g); + + // Generate a keypair + DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator(); + dhGen.Init(new DHKeyGenerationParameters(random, dhParams)); + + AsymmetricCipherKeyPair dhPair = dhGen.GenerateKeyPair(); + + // Store the public value to send to server + this.Yc = ((DHPublicKeyParameters)dhPair.Public).Y; + + // Calculate the shared secret + DHBasicAgreement dhAgree = new DHBasicAgreement(); + dhAgree.Init(dhPair.Private); + + BigInteger agreement = dhAgree.CalculateAgreement(new DHPublicKeyParameters(Ys, dhParams)); + + this.pms = BigIntegers.AsUnsignedByteArray(agreement); + } + + private void validateKeyUsage( + X509CertificateStructure c, + int keyUsageBits) + { + X509Extensions exts = c.TbsCertificate.Extensions; + if (exts != null) + { + X509Extension ext = exts.GetExtension(X509Extensions.KeyUsage); + if (ext != null) + { + DerBitString ku = KeyUsage.GetInstance(ext); + int bits = ku.GetBytes()[0]; + if ((bits & keyUsageBits) != keyUsageBits) + { + this.FailWithError(AL_fatal, AP_certificate_unknown); + } + } + } + } + + private void sendClientCertificate() + { + /* + * just write back the "no client certificate" message + * see also gnutls, auth_cert.c:643 (0B 00 00 03 00 00 00) + */ + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8(HP_CERTIFICATE, bos); + TlsUtilities.WriteUint24(3, bos); + TlsUtilities.WriteUint24(0, bos); + byte[] message = bos.ToArray(); + + rs.WriteMessage((short)RL_HANDSHAKE, message, 0, message.Length); + } + + private void sendClientKeyExchange( + byte[] keData) + { + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8(HP_CLIENT_KEY_EXCHANGE, bos); + TlsUtilities.WriteUint24(keData.Length + 2, bos); + TlsUtilities.WriteUint16(keData.Length, bos); + bos.Write(keData, 0, keData.Length); + byte[] message = bos.ToArray(); + + rs.WriteMessage((short)RL_HANDSHAKE, message, 0, message.Length); + } + + /// Connects to the remote system. + /// Will be used when a certificate is received to verify + /// that this certificate is accepted by the client. + /// If handshake was not successful + public virtual void Connect( + ICertificateVerifyer verifyer) + { + this.verifyer = verifyer; + + /* + * Send Client hello + * + * First, generate some random data. + */ + this.clientRandom = new byte[32]; + + /* + * TLS 1.0 requires a unix-timestamp in the first 4 bytes + */ + int t = (int)(DateTimeUtilities.CurrentUnixMs() / 1000L); + this.clientRandom[0] = (byte)(t >> 24); + this.clientRandom[1] = (byte)(t >> 16); + this.clientRandom[2] = (byte)(t >> 8); + this.clientRandom[3] = (byte)t; + + random.NextBytes(this.clientRandom, 4, 28); + + + MemoryStream outStr = new MemoryStream(); + TlsUtilities.WriteVersion(outStr); + outStr.Write(this.clientRandom, 0, this.clientRandom.Length); + + /* + * Length of Session id + */ + TlsUtilities.WriteUint8((short)0, outStr); + + /* + * Cipher suites + */ + TlsCipherSuiteManager.WriteCipherSuites(outStr); + + /* + * Compression methods, just the null method. + */ + byte[] compressionMethods = new byte[]{0x00}; + TlsUtilities.WriteUint8((short)compressionMethods.Length, outStr); + outStr.Write(compressionMethods, 0, compressionMethods.Length); + + + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8(HP_CLIENT_HELLO, bos); + TlsUtilities.WriteUint24((int) outStr.Length, bos); + byte[] outBytes = outStr.ToArray(); + bos.Write(outBytes, 0, outBytes.Length); + byte[] message = bos.ToArray(); + rs.WriteMessage(RL_HANDSHAKE, message, 0, message.Length); + connection_state = CS_CLIENT_HELLO_SEND; + + /* + * We will now read data, until we have completed the handshake. + */ + while (connection_state != CS_DONE) + { + rs.ReadData(); + } + + this.tlsInputStream = new TlsInputStream(this); + this.tlsOutputStream = new TlsOuputStream(this); + } + + /** + * Read data from the network. The method will return immed, if there is + * still some data left in the buffer, or block untill some application + * data has been read from the network. + * + * @param buf The buffer where the data will be copied to. + * @param offset The position where the data will be placed in the buffer. + * @param len The maximum number of bytes to read. + * @return The number of bytes read. + * @throws IOException If something goes wrong during reading data. + */ + internal int ReadApplicationData(byte[] buf, int offset, int len) + { + while (applicationDataQueue.Available == 0) + { + /* + * We need to read some data. + */ + if (this.failedWithError) + { + /* + * Something went terribly wrong, we should throw an IOException + */ + throw new IOException(TLS_ERROR_MESSAGE); + } + if (this.closed) + { + /* + * Connection has been closed, there is no more data to read. + */ + return 0; + } + + try + { + rs.ReadData(); + } + catch (IOException e) + { + if (!this.closed) + { + this.FailWithError(AL_fatal, AP_internal_error); + } + throw e; + } + catch (Exception e) + { + if (!this.closed) + { + this.FailWithError(AL_fatal, AP_internal_error); + } + throw e; + } + } + len = System.Math.Min(len, applicationDataQueue.Available); + applicationDataQueue.Read(buf, offset, len, 0); + applicationDataQueue.RemoveData(len); + return len; + } + + /** + * Send some application data to the remote system. + *

+ * The method will handle fragmentation internally. + * + * @param buf The buffer with the data. + * @param offset The position in the buffer where the data is placed. + * @param len The length of the data. + * @throws IOException If something goes wrong during sending. + */ + internal void WriteData(byte[] buf, int offset, int len) + { + if (this.failedWithError) + { + throw new IOException(TLS_ERROR_MESSAGE); + } + if (this.closed) + { + throw new IOException("Sorry, connection has been closed, you cannot write more data"); + } + + /* + * Protect against known IV attack! + * + * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT + * YOU ARE DOING HERE. + */ + rs.WriteMessage(RL_APPLICATION_DATA, emptybuf, 0, 0); + + do + { + /* + * We are only allowed to write fragments up to 2^14 bytes. + */ + int toWrite = System.Math.Min(len, 1 << 14); + + try + { + rs.WriteMessage(RL_APPLICATION_DATA, buf, offset, toWrite); + } + catch (IOException e) + { + if (!closed) + { + this.FailWithError(AL_fatal, AP_internal_error); + } + throw e; + } + catch (Exception e) + { + if (!closed) + { + this.FailWithError(AL_fatal, AP_internal_error); + } + throw e; + } + + offset += toWrite; + len -= toWrite; + } + while (len > 0); + } + + [Obsolete("Use 'OutputStream' property instead")] + public TlsOuputStream TlsOuputStream + { + get { return this.tlsOutputStream; } + } + + ///

A Stream which can be used to send data. + public virtual Stream OutputStream + { + get { return this.tlsOutputStream; } + } + + [Obsolete("Use 'InputStream' property instead")] + public TlsInputStream TlsInputStream + { + get { return this.tlsInputStream; } + } + + /// A Stream which can be used to read data. + public virtual Stream InputStream + { + get { return this.tlsInputStream; } + } + + /** + * Terminate this connection whith an alert. + *

+ * Can be used for normal closure too. + * + * @param alertLevel The level of the alert, an be AL_fatal or AL_warning. + * @param alertDescription The exact alert message. + * @throws IOException If alert was fatal. + */ + internal void FailWithError( + short alertLevel, + short alertDescription) + { + /* + * Check if the connection is still open. + */ + if (!closed) + { + /* + * Prepare the message + */ + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + this.closed = true; + + if (alertLevel == AL_fatal) + { + /* + * This is a fatal message. + */ + this.failedWithError = true; + } + rs.WriteMessage(RL_ALERT, error, 0, 2); + rs.Close(); + if (alertLevel == AL_fatal) + { + throw new IOException(TLS_ERROR_MESSAGE); + } + } + else + { + throw new IOException(TLS_ERROR_MESSAGE); + } + } + + ///

Closes this connection + /// If something goes wrong during closing. + public virtual void Close() + { + if (!closed) + { + this.FailWithError((short)1, (short)0); + } + } + + /** + * Make sure the Stream is now empty. Fail otherwise. + * + * @param is The Stream to check. + * @throws IOException If is is not empty. + */ + internal void AssertEmpty( + MemoryStream inStr) + { + if (inStr.Position < inStr.Length) + { + this.FailWithError(AL_fatal, AP_decode_error); + } + } + + internal void Flush() + { + rs.Flush(); + } + } +} diff --git a/src/core/srcbc/crypto/tls/TlsRsaSigner.cs b/src/core/srcbc/crypto/tls/TlsRsaSigner.cs new file mode 100644 index 0000000..a4baa5b --- /dev/null +++ b/src/core/srcbc/crypto/tls/TlsRsaSigner.cs @@ -0,0 +1,17 @@ +using System; + +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Signers; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsRsaSigner + : GenericSigner + { + internal TlsRsaSigner() + : base(new Pkcs1Encoding(new RsaBlindedEngine()), new CombinedHash()) + { + } + } +} diff --git a/src/core/srcbc/math/BigInteger.cs b/src/core/srcbc/math/BigInteger.cs index 0943f68..65e8f3b 100644 --- a/src/core/srcbc/math/BigInteger.cs +++ b/src/core/srcbc/math/BigInteger.cs @@ -2101,9 +2101,7 @@ namespace Org.BouncyCastle.Math return this.sign > 0 ? result : result.Negate(); } - int maxBitLength = this.BitLength + val.BitLength; - int resLength = (maxBitLength + BitsPerInt - 1) / BitsPerInt; - + int resLength = (this.BitLength + val.BitLength) / BitsPerInt + 1; int[] res = new int[resLength]; if (val == this) diff --git a/src/core/srcbc/openpgp/PgpLiteralData.cs b/src/core/srcbc/openpgp/PgpLiteralData.cs index d9fd9b3..b448a0c 100644 --- a/src/core/srcbc/openpgp/PgpLiteralData.cs +++ b/src/core/srcbc/openpgp/PgpLiteralData.cs @@ -36,6 +36,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp get { return data.FileName; } } + /// Return the file name as an unintrepreted byte array. + public byte[] GetRawFileName() + { + return data.GetRawFileName(); + } + /// The modification time for the file. public DateTime ModificationTime { diff --git a/src/core/srcbc/openssl/EncryptionException.cs b/src/core/srcbc/openssl/EncryptionException.cs new file mode 100644 index 0000000..c898255 --- /dev/null +++ b/src/core/srcbc/openssl/EncryptionException.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Security +{ + public class EncryptionException + : IOException + { + public EncryptionException( + string message) + : base(message) + { + } + + public EncryptionException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/src/core/srcbc/openssl/PEMReader.cs b/src/core/srcbc/openssl/PEMReader.cs index 982fc6f..9f3d5dc 100644 --- a/src/core/srcbc/openssl/PEMReader.cs +++ b/src/core/srcbc/openssl/PEMReader.cs @@ -315,12 +315,12 @@ namespace Org.BouncyCastle.OpenSsl if (procType == "4,ENCRYPTED") { if (pFinder == null) - throw new InvalidOperationException("No password finder specified, but a password is required"); + throw new PasswordException("No password finder specified, but a password is required"); char[] password = pFinder.GetPassword(); if (password == null) - throw new IOException("Password is null, but a password is required"); + throw new PasswordException("Password is null, but a password is required"); string dekInfo = (string) fields["DEK-Info"]; string[] tknz = dekInfo.Split(','); diff --git a/src/core/srcbc/openssl/PEMUtilities.cs b/src/core/srcbc/openssl/PEMUtilities.cs index e9671eb..d75bb02 100644 --- a/src/core/srcbc/openssl/PEMUtilities.cs +++ b/src/core/srcbc/openssl/PEMUtilities.cs @@ -39,7 +39,7 @@ namespace Org.BouncyCastle.OpenSsl { string baseAlg, mode; if (!ParseDekAlgName(dekAlgName, out baseAlg, out mode)) - throw new ArgumentException("Unknown DEK algorithm: " + dekAlgName, "dekAlgName"); + throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); string padding; switch (mode) @@ -53,7 +53,7 @@ namespace Org.BouncyCastle.OpenSsl padding = "NoPadding"; break; default: - throw new ArgumentException("Unknown DEK algorithm: " + dekAlgName, "dekAlgName"); + throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); } string algorithm; @@ -87,7 +87,7 @@ namespace Org.BouncyCastle.OpenSsl algorithm = "RC2"; break; default: - throw new ArgumentException("Unknown DEK algorithm: " + dekAlgName, "dekAlgName"); + throw new EncryptionException("Unknown DEK algorithm: " + dekAlgName); } string cipherName = algorithm + "/" + mode + "/" + padding; diff --git a/src/core/srcbc/openssl/PasswordException.cs b/src/core/srcbc/openssl/PasswordException.cs new file mode 100644 index 0000000..e0f3248 --- /dev/null +++ b/src/core/srcbc/openssl/PasswordException.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Security +{ + public class PasswordException + : IOException + { + public PasswordException( + string message) + : base(message) + { + } + + public PasswordException( + string message, + Exception exception) + : base(message, exception) + { + } + } +} diff --git a/src/core/srcbc/pkix/CertStatus.cs b/src/core/srcbc/pkix/CertStatus.cs new file mode 100644 index 0000000..8424045 --- /dev/null +++ b/src/core/srcbc/pkix/CertStatus.cs @@ -0,0 +1,35 @@ +using System; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Pkix +{ + public class CertStatus + { + public const int Unrevoked = 11; + + public const int Undetermined = 12; + + private int status = Unrevoked; + + DateTimeObject revocationDate = null; + + /// + /// Returns the revocationDate. + /// + public DateTimeObject RevocationDate + { + get { return revocationDate; } + set { this.revocationDate = value; } + } + + /// + /// Returns the certStatus. + /// + public int Status + { + get { return status; } + set { this.status = value; } + } + } +} diff --git a/src/core/srcbc/pkix/PkixAttrCertChecker.cs b/src/core/srcbc/pkix/PkixAttrCertChecker.cs new file mode 100644 index 0000000..883a533 --- /dev/null +++ b/src/core/srcbc/pkix/PkixAttrCertChecker.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + public abstract class PkixAttrCertChecker + { + /** + * Returns an immutable Set of X.509 attribute certificate + * extensions that this PkixAttrCertChecker supports or + * null if no extensions are supported. + *

+ * Each element of the set is a String representing the + * Object Identifier (OID) of the X.509 extension that is supported. + *

+ *

+ * All X.509 attribute certificate extensions that a + * PkixAttrCertChecker might possibly be able to process + * should be included in the set. + *

+ * + * @return an immutable Set of X.509 extension OIDs (in + * String format) supported by this + * PkixAttrCertChecker, or null if no + * extensions are supported + */ + public abstract ISet GetSupportedExtensions(); + + /** + * Performs checks on the specified attribute certificate. Every handled + * extension is rmeoved from the unresolvedCritExts + * collection. + * + * @param attrCert The attribute certificate to be checked. + * @param certPath The certificate path which belongs to the attribute + * certificate issuer public key certificate. + * @param holderCertPath The certificate path which belongs to the holder + * certificate. + * @param unresolvedCritExts a Collection of OID strings + * representing the current set of unresolved critical extensions + * @throws CertPathValidatorException if the specified attribute certificate + * does not pass the check. + */ + public abstract void Check(IX509AttributeCertificate attrCert, PkixCertPath certPath, + PkixCertPath holderCertPath, ICollection unresolvedCritExts); + + /** + * Returns a clone of this object. + * + * @return a copy of this PkixAttrCertChecker + */ + public abstract PkixAttrCertChecker Clone(); + } +} diff --git a/src/core/srcbc/pkix/PkixAttrCertPathBuilder.cs b/src/core/srcbc/pkix/PkixAttrCertPathBuilder.cs new file mode 100644 index 0000000..5388605 --- /dev/null +++ b/src/core/srcbc/pkix/PkixAttrCertPathBuilder.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixAttrCertPathBuilder + { + /** + * Build and validate a CertPath using the given parameter. + * + * @param params PKIXBuilderParameters object containing all information to + * build the CertPath + */ + public virtual PkixCertPathBuilderResult Build( + PkixBuilderParameters pkixParams) + { + // search target certificates + + IX509Selector certSelect = pkixParams.GetTargetConstraints(); + if (!(certSelect is X509AttrCertStoreSelector)) + { + throw new PkixCertPathBuilderException( + "TargetConstraints must be an instance of " + + typeof(X509AttrCertStoreSelector).FullName + + " for " + + typeof(PkixAttrCertPathBuilder).FullName + " class."); + } + + ICollection targets; + try + { + targets = PkixCertPathValidatorUtilities.FindCertificates( + (X509AttrCertStoreSelector)certSelect, pkixParams.GetStores()); + } + catch (Exception e) + { + throw new PkixCertPathBuilderException("Error finding target attribute certificate.", e); + } + + if (targets.Count == 0) + { + throw new PkixCertPathBuilderException( + "No attribute certificate found matching targetContraints."); + } + + PkixCertPathBuilderResult result = null; + + // check all potential target certificates + foreach (IX509AttributeCertificate cert in targets) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + X509Name[] principals = cert.Issuer.GetPrincipals(); + ISet issuers = new HashSet(); + for (int i = 0; i < principals.Length; i++) + { + try + { + selector.Subject = principals[i]; + + issuers.AddAll(PkixCertPathValidatorUtilities.FindCertificates(selector, pkixParams.GetStores())); + issuers.AddAll(PkixCertPathValidatorUtilities.FindCertificates(selector, pkixParams.GetX509Stores())); + } + catch (Exception e) + { + throw new PkixCertPathBuilderException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + + if (issuers.IsEmpty) + throw new PkixCertPathBuilderException("Public key certificate for attribute certificate cannot be found."); + + IList certPathList = new ArrayList(); + + foreach (X509Certificate issuer in issuers) + { + result = Build(cert, issuer, pkixParams, certPathList); + + if (result != null) + break; + } + + if (result != null) + break; + } + + if (result == null && certPathException != null) + { + throw new PkixCertPathBuilderException( + "Possible certificate chain could not be validated.", + certPathException); + } + + if (result == null && certPathException == null) + { + throw new PkixCertPathBuilderException( + "Unable to find certificate chain."); + } + + return result; + } + + private Exception certPathException; + + private PkixCertPathBuilderResult Build( + IX509AttributeCertificate attrCert, + X509Certificate tbvCert, + PkixBuilderParameters pkixParams, + IList tbvPath) + { + // If tbvCert is readily present in tbvPath, it indicates having run + // into a cycle in the + // PKI graph. + if (tbvPath.Contains(tbvCert)) + return null; + + // step out, the certificate is not allowed to appear in a certification + // chain + if (pkixParams.GetExcludedCerts().Contains(tbvCert)) + return null; + + // test if certificate path exceeds maximum length + if (pkixParams.MaxPathLength != -1) + { + if (tbvPath.Count - 1 > pkixParams.MaxPathLength) + return null; + } + + tbvPath.Add(tbvCert); + + PkixCertPathBuilderResult builderResult = null; + + X509CertificateParser certParser = new X509CertificateParser(); + PkixAttrCertPathValidator validator = new PkixAttrCertPathValidator(); + + try + { + // check whether the issuer of is a TrustAnchor + if (PkixCertPathValidatorUtilities.FindTrustAnchor(tbvCert, pkixParams.GetTrustAnchors()) != null) + { + PkixCertPath certPath = new PkixCertPath(tbvPath); + PkixCertPathValidatorResult result; + + try + { + result = validator.Validate(certPath, pkixParams); + } + catch (Exception e) + { + throw new Exception("Certification path could not be validated.", e); + } + + return new PkixCertPathBuilderResult(certPath, result.TrustAnchor, + result.PolicyTree, result.SubjectPublicKey); + } + else + { + // add additional X.509 stores from locations in certificate + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromAltNames(tbvCert, pkixParams); + } + catch (CertificateParsingException e) + { + throw new Exception("No additional X.509 stores can be added from certificate locations.", e); + } + + // try to get the issuer certificate from one of the stores + ISet issuers = new HashSet(); + try + { + issuers.AddAll(PkixCertPathValidatorUtilities.FindIssuerCerts(tbvCert, pkixParams)); + } + catch (Exception e) + { + throw new Exception("Cannot find issuer certificate for certificate in certification path.", e); + } + + if (issuers.IsEmpty) + throw new Exception("No issuer certificate for certificate in certification path found."); + + foreach (X509Certificate issuer in issuers) + { + // if untrusted self signed certificate continue + if (PkixCertPathValidatorUtilities.IsSelfIssued(issuer)) + continue; + + builderResult = Build(attrCert, issuer, pkixParams, tbvPath); + + if (builderResult != null) + break; + } + } + } + catch (Exception e) + { + certPathException = new Exception("No valid certification path could be build.", e); + } + + if (builderResult == null) + { + tbvPath.Remove(tbvCert); + } + + return builderResult; + } + } +} diff --git a/src/core/srcbc/pkix/PkixAttrCertPathValidator.cs b/src/core/srcbc/pkix/PkixAttrCertPathValidator.cs new file mode 100644 index 0000000..76c2bd4 --- /dev/null +++ b/src/core/srcbc/pkix/PkixAttrCertPathValidator.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * CertPathValidatorSpi implementation for X.509 Attribute Certificates la RFC 3281. + * + * @see org.bouncycastle.x509.ExtendedPkixParameters + */ + public class PkixAttrCertPathValidator + // extends CertPathValidatorSpi + { + /** + * Validates an attribute certificate with the given certificate path. + * + *

+ * params must be an instance of + * ExtendedPkixParameters. + *

+ * The target constraints in the params must be an + * X509AttrCertStoreSelector with at least the attribute + * certificate criterion set. Obey that also target informations may be + * necessary to correctly validate this attribute certificate. + *

+ * The attribute certificate issuer must be added to the trusted attribute + * issuers with {@link ExtendedPkixParameters#setTrustedACIssuers(Set)}. + *

+ * @param certPath The certificate path which belongs to the attribute + * certificate issuer public key certificate. + * @param params The PKIX parameters. + * @return A PKIXCertPathValidatorResult of the result of + * validating the certPath. + * @throws InvalidAlgorithmParameterException if params is + * inappropriate for this validator. + * @throws CertPathValidatorException if the verification fails. + */ + public virtual PkixCertPathValidatorResult Validate( + PkixCertPath certPath, + PkixParameters pkixParams) + { + IX509Selector certSelect = pkixParams.GetTargetConstraints(); + if (!(certSelect is X509AttrCertStoreSelector)) + { + throw new ArgumentException( + "TargetConstraints must be an instance of " + typeof(X509AttrCertStoreSelector).FullName, + "pkixParams"); + } + IX509AttributeCertificate attrCert = ((X509AttrCertStoreSelector) certSelect).AttributeCert; + + PkixCertPath holderCertPath = Rfc3281CertPathUtilities.ProcessAttrCert1(attrCert, pkixParams); + PkixCertPathValidatorResult result = Rfc3281CertPathUtilities.ProcessAttrCert2(certPath, pkixParams); + X509Certificate issuerCert = (X509Certificate)certPath.Certificates[0]; + Rfc3281CertPathUtilities.ProcessAttrCert3(issuerCert, pkixParams); + Rfc3281CertPathUtilities.ProcessAttrCert4(issuerCert, pkixParams); + Rfc3281CertPathUtilities.ProcessAttrCert5(attrCert, pkixParams); + // 6 already done in X509AttrCertStoreSelector + Rfc3281CertPathUtilities.ProcessAttrCert7(attrCert, certPath, holderCertPath, pkixParams); + Rfc3281CertPathUtilities.AdditionalChecks(attrCert, pkixParams); + DateTime date; + try + { + date = PkixCertPathValidatorUtilities.GetValidCertDateFromValidityModel(pkixParams, null, -1); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Could not get validity date from attribute certificate.", e); + } + Rfc3281CertPathUtilities.CheckCrls(attrCert, pkixParams, issuerCert, date, certPath.Certificates); + return result; + } + } +} diff --git a/src/core/srcbc/pkix/PkixBuilderParameters.cs b/src/core/srcbc/pkix/PkixBuilderParameters.cs new file mode 100644 index 0000000..c29b0c6 --- /dev/null +++ b/src/core/srcbc/pkix/PkixBuilderParameters.cs @@ -0,0 +1,140 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509.Store; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixBuilderParameters. + /// + public class PkixBuilderParameters + : PkixParameters + { + private int maxPathLength = 5; + + private ISet excludedCerts = new HashSet(); + + /** + * Returns an instance of PkixBuilderParameters. + *

+ * This method can be used to get a copy from other + * PKIXBuilderParameters, PKIXParameters, + * and ExtendedPKIXParameters instances. + *

+ * + * @param pkixParams The PKIX parameters to create a copy of. + * @return An PkixBuilderParameters instance. + */ + public static PkixBuilderParameters GetInstance( + PkixParameters pkixParams) + { + PkixBuilderParameters parameters = new PkixBuilderParameters( + pkixParams.GetTrustAnchors(), + new X509CertStoreSelector(pkixParams.GetTargetCertConstraints())); + parameters.SetParams(pkixParams); + return parameters; + } + + public PkixBuilderParameters( + ISet trustAnchors, + IX509Selector targetConstraints) + : base(trustAnchors) + { + SetTargetCertConstraints(targetConstraints); + } + + public virtual int MaxPathLength + { + get { return maxPathLength; } + set + { + if (value < -1) + { + throw new InvalidParameterException( + "The maximum path length parameter can not be less than -1."); + } + this.maxPathLength = value; + } + } + + /// + /// Excluded certificates are not used for building a certification path. + /// + /// the excluded certificates. + public virtual ISet GetExcludedCerts() + { + return new HashSet(excludedCerts); + } + + /// + /// Sets the excluded certificates which are not used for building a + /// certification path. If the ISet is null an + /// empty set is assumed. + /// + /// + /// The given set is cloned to protect it against subsequent modifications. + /// + /// The excluded certificates to set. + public virtual void SetExcludedCerts( + ISet excludedCerts) + { + if (excludedCerts == null) + { + excludedCerts = new HashSet(); + } + else + { + this.excludedCerts = new HashSet(excludedCerts); + } + } + + /** + * Can alse handle ExtendedPKIXBuilderParameters and + * PKIXBuilderParameters. + * + * @param params Parameters to set. + * @see org.bouncycastle.x509.ExtendedPKIXParameters#setParams(java.security.cert.PKIXParameters) + */ + protected override void SetParams( + PkixParameters parameters) + { + base.SetParams(parameters); + if (parameters is PkixBuilderParameters) + { + PkixBuilderParameters _params = (PkixBuilderParameters) parameters; + maxPathLength = _params.maxPathLength; + excludedCerts = new HashSet(_params.excludedCerts); + } + } + + /** + * Makes a copy of this PKIXParameters object. Changes to the + * copy will not affect the original and vice versa. + * + * @return a copy of this PKIXParameters object + */ + public override object Clone() + { + PkixBuilderParameters parameters = new PkixBuilderParameters( + GetTrustAnchors(), GetTargetCertConstraints()); + parameters.SetParams(this); + return parameters; + } + + public override string ToString() + { + string nl = Platform.NewLine; + StringBuilder s = new StringBuilder(); + s.Append("PkixBuilderParameters [" + nl); + s.Append(base.ToString()); + s.Append(" Maximum Path Length: "); + s.Append(MaxPathLength); + s.Append(nl + "]" + nl); + return s.ToString(); + } + } +} diff --git a/src/core/srcbc/pkix/PkixCertPath.cs b/src/core/srcbc/pkix/PkixCertPath.cs new file mode 100644 index 0000000..13c4028 --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPath.cs @@ -0,0 +1,474 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Cms; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Cms; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /** + * An immutable sequence of certificates (a certification path).
+ *
+ * This is an abstract class that defines the methods common to all CertPaths. + * Subclasses can handle different kinds of certificates (X.509, PGP, etc.).
+ *
+ * All CertPath objects have a type, a list of Certificates, and one or more + * supported encodings. Because the CertPath class is immutable, a CertPath + * cannot change in any externally visible way after being constructed. This + * stipulation applies to all public fields and methods of this class and any + * added or overridden by subclasses.
+ *
+ * The type is a string that identifies the type of Certificates in the + * certification path. For each certificate cert in a certification path + * certPath, cert.getType().equals(certPath.getType()) must be true.
+ *
+ * The list of Certificates is an ordered List of zero or more Certificates. + * This List and all of the Certificates contained in it must be immutable.
+ *
+ * Each CertPath object must support one or more encodings so that the object + * can be translated into a byte array for storage or transmission to other + * parties. Preferably, these encodings should be well-documented standards + * (such as PKCS#7). One of the encodings supported by a CertPath is considered + * the default encoding. This encoding is used if no encoding is explicitly + * requested (for the {@link #getEncoded()} method, for instance).
+ *
+ * All CertPath objects are also Serializable. CertPath objects are resolved + * into an alternate {@link CertPathRep} object during serialization. This + * allows a CertPath object to be serialized into an equivalent representation + * regardless of its underlying implementation.
+ *
+ * CertPath objects can be created with a CertificateFactory or they can be + * returned by other classes, such as a CertPathBuilder.
+ *
+ * By convention, X.509 CertPaths (consisting of X509Certificates), are ordered + * starting with the target certificate and ending with a certificate issued by + * the trust anchor. That is, the issuer of one certificate is the subject of + * the following one. The certificate representing the + * {@link TrustAnchor TrustAnchor} should not be included in the certification + * path. Unvalidated X.509 CertPaths may not follow these conventions. PKIX + * CertPathValidators will detect any departure from these conventions that + * cause the certification path to be invalid and throw a + * CertPathValidatorException.
+ *
+ * Concurrent Access
+ *
+ * All CertPath objects must be thread-safe. That is, multiple threads may + * concurrently invoke the methods defined in this class on a single CertPath + * object (or more than one) with no ill effects. This is also true for the List + * returned by CertPath.getCertificates.
+ *
+ * Requiring CertPath objects to be immutable and thread-safe allows them to be + * passed around to various pieces of code without worrying about coordinating + * access. Providing this thread-safety is generally not difficult, since the + * CertPath and List objects in question are immutable. + * + * @see CertificateFactory + * @see CertPathBuilder + */ + /// + /// CertPath implementation for X.509 certificates. + /// + public class PkixCertPath +// : CertPath + { + internal static IList certPathEncodings; + + static PkixCertPath() + { + IList encodings = new ArrayList(); + + encodings.Add("PkiPath"); + encodings.Add("PEM"); + encodings.Add("PKCS7"); + certPathEncodings = ArrayList.ReadOnly(encodings); + } + + private IList certificates = new ArrayList(); + + /** + * @param certs + */ + private static IList SortCerts( + IList certs) + { + if (certs.Count < 2) + return certs; + + X509Name issuer = ((X509Certificate)certs[0]).IssuerDN; + bool okay = true; + + for (int i = 1; i != certs.Count; i++) + { + X509Certificate cert = (X509Certificate)certs[i]; + + if (issuer.Equivalent(cert.SubjectDN, true)) + { + issuer = ((X509Certificate)certs[i]).IssuerDN; + } + else + { + okay = false; + break; + } + } + + if (okay) + return certs; + + // find end-entity cert + IList retList = new ArrayList(certs.Count); + IList orig = new ArrayList(certs); + + for (int i = 0; i < certs.Count; i++) + { + X509Certificate cert = (X509Certificate)certs[i]; + bool found = false; + + X509Name subject = cert.SubjectDN; + foreach (X509Certificate c in certs) + { + if (c.IssuerDN.Equivalent(subject, true)) + { + found = true; + break; + } + } + + if (!found) + { + retList.Add(cert); + certs.RemoveAt(i); + } + } + + // can only have one end entity cert - something's wrong, give up. + if (retList.Count > 1) + return orig; + + for (int i = 0; i != retList.Count; i++) + { + issuer = ((X509Certificate)retList[i]).IssuerDN; + + for (int j = 0; j < certs.Count; j++) + { + X509Certificate c = (X509Certificate)certs[j]; + if (issuer.Equivalent(c.SubjectDN, true)) + { + retList.Add(c); + certs.RemoveAt(j); + break; + } + } + } + + // make sure all certificates are accounted for. + if (certs.Count > 0) + return orig; + + return retList; + } + + /** + * Creates a CertPath of the specified type. + * This constructor is protected because most users should use + * a CertificateFactory to create CertPaths. + * @param type the standard name of the type of Certificatesin this path + **/ + public PkixCertPath( + ICollection certificates) +// : base("X.509") + { + this.certificates = SortCerts(new ArrayList(certificates)); + } + + public PkixCertPath( + Stream inStream) + : this(inStream, "PkiPath") + { + } + + /** + * Creates a CertPath of the specified type. + * This constructor is protected because most users should use + * a CertificateFactory to create CertPaths. + * + * @param type the standard name of the type of Certificatesin this path + **/ + public PkixCertPath( + Stream inStream, + String encoding) +// : base("X.509") + { + try + { + if (encoding.ToUpper().Equals("PkiPath".ToUpper())) + { + Asn1InputStream derInStream = new Asn1InputStream(inStream); + Asn1Object derObject = derInStream.ReadObject(); + if (!(derObject is Asn1Sequence)) + { + throw new CertificateException( + "input stream does not contain a ASN1 SEQUENCE while reading PkiPath encoded data to load CertPath"); + } + IEnumerator e = ((Asn1Sequence)derObject).GetEnumerator(); + Stream certInStream; + MemoryStream outStream; + DerOutputStream derOutStream; + certificates = new ArrayList(); + + while (e.MoveNext()) + { + outStream = new MemoryStream(); + derOutStream = new DerOutputStream(outStream); + + derOutStream.WriteObject((Asn1Encodable)e.Current); + derOutStream.Close(); + + certInStream = new MemoryStream(outStream.ToArray(), false); + certificates.Insert(0, new X509CertificateParser().ReadCertificate(certInStream)); + } + } + else if (encoding.ToUpper().Equals("PKCS7") + || encoding.ToUpper().Equals("PEM")) + { + inStream = new BufferedStream(inStream); + certificates = new ArrayList(); + + X509CertificateParser certParser = new X509CertificateParser(); + X509Certificate cert = null; + + while ((cert = certParser.ReadCertificate(inStream)) != null) + { + certificates.Add(cert); + } + } + else + { + throw new CertificateException("unsupported encoding: " + encoding); + } + } + catch (IOException ex) + { + throw new CertificateException( + "IOException throw while decoding CertPath:\n" + + ex.ToString()); + } + + this.certificates = SortCerts(certificates); + } + + /** + * Returns an iteration of the encodings supported by this + * certification path, with the default encoding + * first. Attempts to modify the returned Iterator via its + * remove method result in an UnsupportedOperationException. + * + * @return an Iterator over the names of the supported encodings (as Strings) + **/ + public virtual IEnumerable Encodings + { + get { return new EnumerableProxy(certPathEncodings); } + } + + /** + * Compares this certification path for equality with the specified object. + * Two CertPaths are equal if and only if their types are equal and their + * certificate Lists (and by implication the Certificates in those Lists) + * are equal. A CertPath is never equal to an object that is not a CertPath.
+ *
+ * This algorithm is implemented by this method. If it is overridden, the + * behavior specified here must be maintained. + * + * @param other + * the object to test for equality with this certification path + * + * @return true if the specified object is equal to this certification path, + * false otherwise + * + * @see Object#hashCode() Object.hashCode() + */ + public override bool Equals( + object obj) + { + if (this == obj) + return true; + + PkixCertPath other = obj as PkixCertPath; + if (other == null) + return false; + +// if (!this.Type.Equals(other.Type)) +// return false; + + //return this.Certificates.Equals(other.Certificates); + + // TODO Extract this to a utility class + IList thisCerts = this.Certificates; + IList otherCerts = other.Certificates; + + if (thisCerts.Count != otherCerts.Count) + return false; + + IEnumerator e1 = thisCerts.GetEnumerator(); + IEnumerator e2 = thisCerts.GetEnumerator(); + + while (e1.MoveNext()) + { + e2.MoveNext(); + + if (!Platform.Equals(e1.Current, e2.Current)) + return false; + } + + return true; + } + + public override int GetHashCode() + { + // FIXME? + return this.Certificates.GetHashCode(); + } + + /** + * Returns the encoded form of this certification path, using + * the default encoding. + * + * @return the encoded bytes + * @exception CertificateEncodingException if an encoding error occurs + **/ + public virtual byte[] GetEncoded() + { + foreach (object enc in Encodings) + { + if (enc is string) + { + return GetEncoded((string)enc); + } + } + return null; + } + + /** + * Returns the encoded form of this certification path, using + * the specified encoding. + * + * @param encoding the name of the encoding to use + * @return the encoded bytes + * @exception CertificateEncodingException if an encoding error + * occurs or the encoding requested is not supported + * + */ + public virtual byte[] GetEncoded( + string encoding) + { + if (String.Compare(encoding, "PkiPath", true) == 0) + { + Asn1EncodableVector v = new Asn1EncodableVector(); + + for (int i = certificates.Count - 1; i >= 0; i--) + { + v.Add(ToAsn1Object((X509Certificate) certificates[i])); + } + + return ToDerEncoded(new DerSequence(v)); + } + else if (String.Compare(encoding, "PKCS7", true) == 0) + { + Asn1.Pkcs.ContentInfo encInfo = new Asn1.Pkcs.ContentInfo( + PkcsObjectIdentifiers.Data, null); + + Asn1EncodableVector v = new Asn1EncodableVector(); + for (int i = 0; i != certificates.Count; i++) + { + v.Add(ToAsn1Object((X509Certificate)certificates[i])); + } + + Asn1.Pkcs.SignedData sd = new Asn1.Pkcs.SignedData( + new DerInteger(1), + new DerSet(), + encInfo, + new DerSet(v), + null, + new DerSet()); + + return ToDerEncoded(new Asn1.Pkcs.ContentInfo(PkcsObjectIdentifiers.SignedData, sd)); + } + else if (String.Compare(encoding, "PEM", true) == 0) + { + MemoryStream bOut = new MemoryStream(); + PemWriter pWrt = new PemWriter(new StreamWriter(bOut)); + + try + { + for (int i = 0; i != certificates.Count; i++) + { + pWrt.WriteObject(certificates[i]); + } + + pWrt.Writer.Close(); + } + catch (Exception) + { + throw new CertificateEncodingException("can't encode certificate for PEM encoded path"); + } + + return bOut.ToArray(); + } + else + { + throw new CertificateEncodingException("unsupported encoding: " + encoding); + } + } + + /// + /// Returns the list of certificates in this certification + /// path. + /// + public virtual IList Certificates + { + get { return ArrayList.ReadOnly(certificates); } + } + + /** + * Return a DERObject containing the encoded certificate. + * + * @param cert the X509Certificate object to be encoded + * + * @return the DERObject + **/ + private Asn1Object ToAsn1Object( + X509Certificate cert) + { + try + { + return Asn1Object.FromByteArray(cert.GetEncoded()); + } + catch (Exception e) + { + throw new CertificateEncodingException("Exception while encoding certificate", e); + } + } + + private byte[] ToDerEncoded(Asn1Encodable obj) + { + try + { + return obj.GetEncoded(Asn1Encodable.Der); + } + catch (IOException e) + { + throw new CertificateEncodingException("Exception thrown", e); + } + } + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathBuilder.cs b/src/core/srcbc/pkix/PkixCertPathBuilder.cs new file mode 100644 index 0000000..a0d43f9 --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathBuilder.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Asn1.IsisMtt; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * Implements the PKIX CertPathBuilding algorithm for BouncyCastle. + * + * @see CertPathBuilderSpi + */ + public class PkixCertPathBuilder + // : CertPathBuilderSpi + { + /** + * Build and validate a CertPath using the given parameter. + * + * @param params PKIXBuilderParameters object containing all information to + * build the CertPath + */ + public virtual PkixCertPathBuilderResult Build( + PkixBuilderParameters pkixParams) + { + // search target certificates + + IX509Selector certSelect = pkixParams.GetTargetCertConstraints(); + if (!(certSelect is X509CertStoreSelector)) + { + throw new PkixCertPathBuilderException( + "TargetConstraints must be an instance of " + + typeof(X509CertStoreSelector).FullName + " for " + + this.GetType() + " class."); + } + + ISet targets = new HashSet(); + try + { + targets.AddAll(PkixCertPathValidatorUtilities.FindCertificates((X509CertStoreSelector)certSelect, pkixParams.GetStores())); + targets.AddAll(PkixCertPathValidatorUtilities.FindCertificates((X509CertStoreSelector)certSelect, pkixParams.GetX509Stores())); + // TODO Should this include an entry for pkixParams.GetAdditionalStores() too? + } + catch (Exception e) + { + throw new PkixCertPathBuilderException( + "Error finding target certificate.", e); + } + + if (targets.IsEmpty) + throw new PkixCertPathBuilderException("No certificate found matching targetContraints."); + + PkixCertPathBuilderResult result = null; + IList certPathList = new ArrayList(); + + // check all potential target certificates + foreach (X509Certificate cert in targets) + { + result = Build(cert, pkixParams, certPathList); + + if (result != null) + break; + } + + if (result == null && certPathException != null) + { + throw new PkixCertPathBuilderException(certPathException.Message, certPathException.InnerException); + } + + if (result == null && certPathException == null) + { + throw new PkixCertPathBuilderException("Unable to find certificate chain."); + } + + return result; + } + + private Exception certPathException; + + protected virtual PkixCertPathBuilderResult Build( + X509Certificate tbvCert, + PkixBuilderParameters pkixParams, + IList tbvPath) + { + // If tbvCert is readily present in tbvPath, it indicates having run + // into a cycle in the PKI graph. + if (tbvPath.Contains(tbvCert)) + return null; + + // step out, the certificate is not allowed to appear in a certification + // chain. + if (pkixParams.GetExcludedCerts().Contains(tbvCert)) + return null; + + // test if certificate path exceeds maximum length + if (pkixParams.MaxPathLength != -1) + { + if (tbvPath.Count - 1 > pkixParams.MaxPathLength) + return null; + } + + tbvPath.Add(tbvCert); + + X509CertificateParser certParser = new X509CertificateParser(); + PkixCertPathBuilderResult builderResult = null; + PkixCertPathValidator validator = new PkixCertPathValidator(); + + try + { + // check whether the issuer of is a TrustAnchor + if (PkixCertPathValidatorUtilities.FindTrustAnchor(tbvCert, pkixParams.GetTrustAnchors()) != null) + { + // exception message from possibly later tried certification + // chains + PkixCertPath certPath = null; + try + { + certPath = new PkixCertPath(tbvPath); + } + catch (Exception e) + { + throw new Exception( + "Certification path could not be constructed from certificate list.", + e); + } + + PkixCertPathValidatorResult result = null; + try + { + result = (PkixCertPathValidatorResult)validator.Validate( + certPath, pkixParams); + } + catch (Exception e) + { + throw new Exception( + "Certification path could not be validated.", e); + } + + return new PkixCertPathBuilderResult(certPath, result.TrustAnchor, + result.PolicyTree, result.SubjectPublicKey); + } + else + { + // add additional X.509 stores from locations in certificate + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromAltNames( + tbvCert, pkixParams); + } + catch (CertificateParsingException e) + { + throw new Exception( + "No additiontal X.509 stores can be added from certificate locations.", + e); + } + + // try to get the issuer certificate from one of the stores + HashSet issuers = new HashSet(); + try + { + issuers.AddAll(PkixCertPathValidatorUtilities.FindIssuerCerts(tbvCert, pkixParams)); + } + catch (Exception e) + { + throw new Exception( + "Cannot find issuer certificate for certificate in certification path.", + e); + } + + if (issuers.IsEmpty) + throw new Exception("No issuer certificate for certificate in certification path found."); + + foreach (X509Certificate issuer in issuers) + { + builderResult = Build(issuer, pkixParams, tbvPath); + + if (builderResult != null) + break; + } + } + } + catch (Exception e) + { + certPathException = e; + } + + if (builderResult == null) + { + tbvPath.Remove(tbvCert); + } + + return builderResult; + } + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathBuilderException.cs b/src/core/srcbc/pkix/PkixCertPathBuilderException.cs new file mode 100644 index 0000000..9bbd29d --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathBuilderException.cs @@ -0,0 +1,19 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathBuilderException. + /// + public class PkixCertPathBuilderException : GeneralSecurityException + { + public PkixCertPathBuilderException() : base() { } + + public PkixCertPathBuilderException(string message) : base(message) { } + + public PkixCertPathBuilderException(string message, Exception exception) : base(message, exception) { } + + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathBuilderResult.cs b/src/core/srcbc/pkix/PkixCertPathBuilderResult.cs new file mode 100644 index 0000000..acf14b8 --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathBuilderResult.cs @@ -0,0 +1,45 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Pkix; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathBuilderResult. + /// + public class PkixCertPathBuilderResult + : PkixCertPathValidatorResult//, ICertPathBuilderResult + { + private PkixCertPath certPath; + + public PkixCertPathBuilderResult( + PkixCertPath certPath, + TrustAnchor trustAnchor, + PkixPolicyNode policyTree, + AsymmetricKeyParameter subjectPublicKey) + : base(trustAnchor, policyTree, subjectPublicKey) + { + if (certPath == null) + throw new ArgumentNullException("certPath"); + + this.certPath = certPath; + } + + public PkixCertPath CertPath + { + get { return certPath; } + } + + public override string ToString() + { + StringBuilder s = new StringBuilder(); + s.Append("SimplePKIXCertPathBuilderResult: [\n"); + s.Append(" Certification Path: ").Append(CertPath).Append('\n'); + s.Append(" Trust Anchor: ").Append(this.TrustAnchor.TrustedCert.IssuerDN.ToString()).Append('\n'); + s.Append(" Subject Public Key: ").Append(this.SubjectPublicKey).Append("\n]"); + return s.ToString(); + } + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathChecker.cs b/src/core/srcbc/pkix/PkixCertPathChecker.cs new file mode 100644 index 0000000..b28a88b --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathChecker.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + public abstract class PkixCertPathChecker + { + protected PkixCertPathChecker() + { + } + + /** + * Initializes the internal state of this PKIXCertPathChecker. + *

+ * The forward flag specifies the order that certificates + * will be passed to the {@link #check check} method (forward or reverse). A + * PKIXCertPathChecker must support reverse checking + * and may support forward checking. + *

+ * + * @param forward + * the order that certificates are presented to the + * check method. If true, + * certificates are presented from target to most-trusted CA + * (forward); if false, from most-trusted CA to + * target (reverse). + * @exception CertPathValidatorException + * if this PKIXCertPathChecker is unable to + * check certificates in the specified order; it should never + * be thrown if the forward flag is false since reverse + * checking must be supported + */ + public abstract void Init(bool forward); + //throws CertPathValidatorException; + + /** + * Indicates if forward checking is supported. Forward checking refers to + * the ability of the PKIXCertPathChecker to perform its + * checks when certificates are presented to the check method + * in the forward direction (from target to most-trusted CA). + * + * @return true if forward checking is supported, + * false otherwise + */ + public abstract bool IsForwardCheckingSupported(); + + /** + * Returns an immutable Set of X.509 certificate extensions + * that this PKIXCertPathChecker supports (i.e. recognizes, + * is able to process), or null if no extensions are + * supported. + *

+ * Each element of the set is a String representing the + * Object Identifier (OID) of the X.509 extension that is supported. The OID + * is represented by a set of nonnegative integers separated by periods. + *

+ * All X.509 certificate extensions that a PKIXCertPathChecker + * might possibly be able to process should be included in the set. + *

+ * + * @return an immutable Set of X.509 extension OIDs (in + * String format) supported by this + * PKIXCertPathChecker, or null if no + * extensions are supported + */ + public abstract ISet GetSupportedExtensions(); + + /** + * Performs the check(s) on the specified certificate using its internal + * state and removes any critical extensions that it processes from the + * specified collection of OID strings that represent the unresolved + * critical extensions. The certificates are presented in the order + * specified by the init method. + * + * @param cert + * the Certificate to be checked + * @param unresolvedCritExts + * a Collection of OID strings representing the + * current set of unresolved critical extensions + * @exception CertPathValidatorException + * if the specified certificate does not pass the check + */ + public abstract void Check(X509Certificate cert, ICollection unresolvedCritExts); + //throws CertPathValidatorException; + + /** + * Returns a clone of this object. Calls the Object.clone() + * method. All subclasses which maintain state must support and override + * this method, if necessary. + * + * @return a copy of this PKIXCertPathChecker + */ + public virtual object Clone() + { + // TODO Check this + return base.MemberwiseClone(); + } + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathValidator.cs b/src/core/srcbc/pkix/PkixCertPathValidator.cs new file mode 100644 index 0000000..a03cd3f --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathValidator.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * The Service Provider Interface (SPI) + * for the {@link CertPathValidator CertPathValidator} class. All + * CertPathValidator implementations must include a class (the + * SPI class) that extends this class (CertPathValidatorSpi) + * and implements all of its methods. In general, instances of this class + * should only be accessed through the CertPathValidator class. + * For details, see the Java Cryptography Architecture.
+ *
+ * Concurrent Access
+ *
+ * Instances of this class need not be protected against concurrent + * access from multiple threads. Threads that need to access a single + * CertPathValidatorSpi instance concurrently should synchronize + * amongst themselves and provide the necessary locking before calling the + * wrapping CertPathValidator object.
+ *
+ * However, implementations of CertPathValidatorSpi may still + * encounter concurrency issues, since multiple threads each + * manipulating a different CertPathValidatorSpi instance need not + * synchronize. + */ + /// + /// CertPathValidatorSpi implementation for X.509 Certificate validation a la RFC + /// 3280. + /// + public class PkixCertPathValidator + { + public virtual PkixCertPathValidatorResult Validate( + PkixCertPath certPath, + PkixParameters paramsPkix) + { + if (paramsPkix.GetTrustAnchors() == null) + { + throw new ArgumentException( + "trustAnchors is null, this is not allowed for certification path validation.", + "parameters"); + } + + // + // 6.1.1 - inputs + // + + // + // (a) + // + IList certs = certPath.Certificates; + int n = certs.Count; + + if (certs.Count == 0) + throw new PkixCertPathValidatorException("Certification path is empty.", null, certPath, 0); + + // + // (b) + // + // DateTime validDate = PkixCertPathValidatorUtilities.GetValidDate(paramsPkix); + + // + // (c) + // + ISet userInitialPolicySet = paramsPkix.GetInitialPolicies(); + + // + // (d) + // + TrustAnchor trust; + try + { + trust = PkixCertPathValidatorUtilities.FindTrustAnchor( + (X509Certificate)certs[certs.Count - 1], + paramsPkix.GetTrustAnchors()); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException(e.Message, e, certPath, certs.Count - 1); + } + + if (trust == null) + throw new PkixCertPathValidatorException("Trust anchor for certification path not found.", null, certPath, -1); + + // + // (e), (f), (g) are part of the paramsPkix object. + // + IEnumerator certIter; + int index = 0; + int i; + // Certificate for each interation of the validation loop + // Signature information for each iteration of the validation loop + // + // 6.1.2 - setup + // + + // + // (a) + // + IList[] policyNodes = new ArrayList[n + 1]; + for (int j = 0; j < policyNodes.Length; j++) + { + policyNodes[j] = new ArrayList(); + } + + ISet policySet = new HashSet(); + + policySet.Add(Rfc3280CertPathUtilities.ANY_POLICY); + + PkixPolicyNode validPolicyTree = new PkixPolicyNode(new ArrayList(), 0, policySet, null, new HashSet(), + Rfc3280CertPathUtilities.ANY_POLICY, false); + + policyNodes[0].Add(validPolicyTree); + + // + // (b) and (c) + // + PkixNameConstraintValidator nameConstraintValidator = new PkixNameConstraintValidator(); + + // (d) + // + int explicitPolicy; + ISet acceptablePolicies = new HashSet(); + + if (paramsPkix.IsExplicitPolicyRequired) + { + explicitPolicy = 0; + } + else + { + explicitPolicy = n + 1; + } + + // + // (e) + // + int inhibitAnyPolicy; + + if (paramsPkix.IsAnyPolicyInhibited) + { + inhibitAnyPolicy = 0; + } + else + { + inhibitAnyPolicy = n + 1; + } + + // + // (f) + // + int policyMapping; + + if (paramsPkix.IsPolicyMappingInhibited) + { + policyMapping = 0; + } + else + { + policyMapping = n + 1; + } + + // + // (g), (h), (i), (j) + // + AsymmetricKeyParameter workingPublicKey; + X509Name workingIssuerName; + + X509Certificate sign = trust.TrustedCert; + try + { + if (sign != null) + { + workingIssuerName = sign.SubjectDN; + workingPublicKey = sign.GetPublicKey(); + } + else + { + workingIssuerName = new X509Name(trust.CAName); + workingPublicKey = trust.CAPublicKey; + } + } + catch (ArgumentException ex) + { + throw new PkixCertPathValidatorException("Subject of trust anchor could not be (re)encoded.", ex, certPath, + -1); + } + + AlgorithmIdentifier workingAlgId = null; + try + { + workingAlgId = PkixCertPathValidatorUtilities.GetAlgorithmIdentifier(workingPublicKey); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException( + "Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1); + } + + DerObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.ObjectID; + Asn1Encodable workingPublicKeyParameters = workingAlgId.Parameters; + + // + // (k) + // + int maxPathLength = n; + + // + // 6.1.3 + // + + X509CertStoreSelector certConstraints = paramsPkix.GetTargetCertConstraints(); + if (certConstraints != null && !certConstraints.Match((X509Certificate)certs[0])) + { + throw new PkixCertPathValidatorException( + "Target certificate in certification path does not match targetConstraints.", null, certPath, 0); + } + + // + // initialize CertPathChecker's + // + IList pathCheckers = paramsPkix.GetCertPathCheckers(); + certIter = pathCheckers.GetEnumerator(); + + while (certIter.MoveNext()) + { + ((PkixCertPathChecker)certIter.Current).Init(false); + } + + X509Certificate cert = null; + + for (index = certs.Count - 1; index >= 0; index--) + { + // try + // { + // + // i as defined in the algorithm description + // + i = n - index; + + // + // set certificate to be checked in this round + // sign and workingPublicKey and workingIssuerName are set + // at the end of the for loop and initialized the + // first time from the TrustAnchor + // + cert = (X509Certificate)certs[index]; + + // + // 6.1.3 + // + + Rfc3280CertPathUtilities.ProcessCertA(certPath, paramsPkix, index, workingPublicKey, + workingIssuerName, sign); + + Rfc3280CertPathUtilities.ProcessCertBC(certPath, index, nameConstraintValidator); + + validPolicyTree = Rfc3280CertPathUtilities.ProcessCertD(certPath, index, + acceptablePolicies, validPolicyTree, policyNodes, inhibitAnyPolicy); + + validPolicyTree = Rfc3280CertPathUtilities.ProcessCertE(certPath, index, validPolicyTree); + + Rfc3280CertPathUtilities.ProcessCertF(certPath, index, validPolicyTree, explicitPolicy); + + // + // 6.1.4 + // + + if (i != n) + { + if (cert != null && cert.Version == 1) + { + throw new PkixCertPathValidatorException( + "Version 1 certificates can't be used as CA ones.", null, certPath, index); + } + + Rfc3280CertPathUtilities.PrepareNextCertA(certPath, index); + + validPolicyTree = Rfc3280CertPathUtilities.PrepareCertB(certPath, index, policyNodes, + validPolicyTree, policyMapping); + + Rfc3280CertPathUtilities.PrepareNextCertG(certPath, index, nameConstraintValidator); + + // (h) + explicitPolicy = Rfc3280CertPathUtilities.PrepareNextCertH1(certPath, index, explicitPolicy); + policyMapping = Rfc3280CertPathUtilities.PrepareNextCertH2(certPath, index, policyMapping); + inhibitAnyPolicy = Rfc3280CertPathUtilities.PrepareNextCertH3(certPath, index, inhibitAnyPolicy); + + // + // (i) + // + explicitPolicy = Rfc3280CertPathUtilities.PrepareNextCertI1(certPath, index, explicitPolicy); + policyMapping = Rfc3280CertPathUtilities.PrepareNextCertI2(certPath, index, policyMapping); + + // (j) + inhibitAnyPolicy = Rfc3280CertPathUtilities.PrepareNextCertJ(certPath, index, inhibitAnyPolicy); + + // (k) + Rfc3280CertPathUtilities.PrepareNextCertK(certPath, index); + + // (l) + maxPathLength = Rfc3280CertPathUtilities.PrepareNextCertL(certPath, index, maxPathLength); + + // (m) + maxPathLength = Rfc3280CertPathUtilities.PrepareNextCertM(certPath, index, maxPathLength); + + // (n) + Rfc3280CertPathUtilities.PrepareNextCertN(certPath, index); + + ISet criticalExtensions1 = cert.GetCriticalExtensionOids(); + + if (criticalExtensions1 != null) + { + criticalExtensions1 = new HashSet(criticalExtensions1); + + // these extensions are handled by the algorithm + criticalExtensions1.Remove(X509Extensions.KeyUsage.Id); + criticalExtensions1.Remove(X509Extensions.CertificatePolicies.Id); + criticalExtensions1.Remove(X509Extensions.PolicyMappings.Id); + criticalExtensions1.Remove(X509Extensions.InhibitAnyPolicy.Id); + criticalExtensions1.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions1.Remove(X509Extensions.DeltaCrlIndicator.Id); + criticalExtensions1.Remove(X509Extensions.PolicyConstraints.Id); + criticalExtensions1.Remove(X509Extensions.BasicConstraints.Id); + criticalExtensions1.Remove(X509Extensions.SubjectAlternativeName.Id); + criticalExtensions1.Remove(X509Extensions.NameConstraints.Id); + } + else + { + criticalExtensions1 = new HashSet(); + } + + // (o) + Rfc3280CertPathUtilities.PrepareNextCertO(certPath, index, criticalExtensions1, pathCheckers); + + // set signing certificate for next round + sign = cert; + + // (c) + workingIssuerName = sign.SubjectDN; + + // (d) + try + { + workingPublicKey = PkixCertPathValidatorUtilities.GetNextWorkingKey(certPath.Certificates, index); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException("Next working key could not be retrieved.", e, certPath, index); + } + + workingAlgId = PkixCertPathValidatorUtilities.GetAlgorithmIdentifier(workingPublicKey); + // (f) + workingPublicKeyAlgorithm = workingAlgId.ObjectID; + // (e) + workingPublicKeyParameters = workingAlgId.Parameters; + } + } + + // + // 6.1.5 Wrap-up procedure + // + + explicitPolicy = Rfc3280CertPathUtilities.WrapupCertA(explicitPolicy, cert); + + explicitPolicy = Rfc3280CertPathUtilities.WrapupCertB(certPath, index + 1, explicitPolicy); + + // + // (c) (d) and (e) are already done + // + + // + // (f) + // + ISet criticalExtensions = cert.GetCriticalExtensionOids(); + + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + + // Requires .Id + // these extensions are handled by the algorithm + criticalExtensions.Remove(X509Extensions.KeyUsage.Id); + criticalExtensions.Remove(X509Extensions.CertificatePolicies.Id); + criticalExtensions.Remove(X509Extensions.PolicyMappings.Id); + criticalExtensions.Remove(X509Extensions.InhibitAnyPolicy.Id); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + criticalExtensions.Remove(X509Extensions.PolicyConstraints.Id); + criticalExtensions.Remove(X509Extensions.BasicConstraints.Id); + criticalExtensions.Remove(X509Extensions.SubjectAlternativeName.Id); + criticalExtensions.Remove(X509Extensions.NameConstraints.Id); + criticalExtensions.Remove(X509Extensions.CrlDistributionPoints.Id); + } + else + { + criticalExtensions = new HashSet(); + } + + Rfc3280CertPathUtilities.WrapupCertF(certPath, index + 1, pathCheckers, criticalExtensions); + + PkixPolicyNode intersection = Rfc3280CertPathUtilities.WrapupCertG(certPath, paramsPkix, userInitialPolicySet, + index + 1, policyNodes, validPolicyTree, acceptablePolicies); + + if ((explicitPolicy > 0) || (intersection != null)) + { + return new PkixCertPathValidatorResult(trust, intersection, cert.GetPublicKey()); + } + + throw new PkixCertPathValidatorException("Path processing failed on policy.", null, certPath, index); + } + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathValidatorException.cs b/src/core/srcbc/pkix/PkixCertPathValidatorException.cs new file mode 100644 index 0000000..67c158d --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathValidatorException.cs @@ -0,0 +1,218 @@ +using System; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /** + * An exception indicating one of a variety of problems encountered when + * validating a certification path.
+ *
+ * A CertPathValidatorException provides support for wrapping + * exceptions. The {@link #getCause getCause} method returns the throwable, + * if any, that caused this exception to be thrown.
+ *
+ * A CertPathValidatorException may also include the + * certification path that was being validated when the exception was thrown + * and the index of the certificate in the certification path that caused the + * exception to be thrown. Use the {@link #getCertPath getCertPath} and + * {@link #getIndex getIndex} methods to retrieve this information.
+ *
+ * Concurrent Access
+ *
+ * Unless otherwise specified, the methods defined in this class are not + * thread-safe. Multiple threads that need to access a single + * object concurrently should synchronize amongst themselves and + * provide the necessary locking. Multiple threads each manipulating + * separate objects need not synchronize. + * + * @see CertPathValidator + **/ + + public class PkixCertPathValidatorException : GeneralSecurityException + { + private Exception cause; + private PkixCertPath certPath; + private int index = -1; + + public PkixCertPathValidatorException() : base() { } + + /// + /// Creates a PkixCertPathValidatorException with the given detail + /// message. A detail message is a String that describes this + /// particular exception. + /// + /// the detail message + public PkixCertPathValidatorException(string message) : base(message) { } + + /// + /// Creates a PkixCertPathValidatorException with the specified + /// detail message and cause. + /// + /// the detail message + /// the cause (which is saved for later retrieval by the + /// {@link #getCause getCause()} method). (A null + /// value is permitted, and indicates that the cause is + /// nonexistent or unknown.) + public PkixCertPathValidatorException(string message, Exception cause) : base(message) + { + this.cause = cause; + } + + /// + /// Creates a PkixCertPathValidatorException with the specified + /// detail message, cause, certification path, and index. + /// + /// the detail message (or null if none) + /// the cause (or null if none) + /// the certification path that was in the process of being + /// validated when the error was encountered + /// the index of the certificate in the certification path that * + public PkixCertPathValidatorException( + string message, + Exception cause, + PkixCertPath certPath, + int index) + : base(message) + { + if (certPath == null && index != -1) + { + throw new ArgumentNullException( + "certPath = null and index != -1"); + } + if (index < -1 + || (certPath != null && index >= certPath.Certificates.Count)) + { + throw new IndexOutOfRangeException( + " index < -1 or out of bound of certPath.getCertificates()"); + } + + this.cause = cause; + this.certPath = certPath; + this.index = index; + } + + // + // Prints a stack trace to a PrintWriter, including the + // backtrace of the cause, if any. + // + // @param pw + // the PrintWriter to use for output + // + // public void printStackTrace(PrintWriter pw) + // { + // super.printStackTrace(pw); + // if (getCause() != null) + // { + // getCause().printStackTrace(pw); + // } + // } + //} + + + // /** + // * Creates a CertPathValidatorException that wraps the + // * specified throwable. This allows any exception to be converted into a + // * CertPathValidatorException, while retaining information + // * about the wrapped exception, which may be useful for debugging. The + // * detail message is set to (cause==null ? null : cause.toString() + // * ) + // * (which typically contains the class and detail message of cause). + // * + // * @param cause + // * the cause (which is saved for later retrieval by the + // * {@link #getCause getCause()} method). (A null + // * value is permitted, and indicates that the cause is + // * nonexistent or unknown.) + // */ + // public PkixCertPathValidatorException(Throwable cause) + // { + // this.cause = cause; + // } + // + + /// + /// Returns the detail message for this CertPathValidatorException. + /// + /// the detail message, or null if neither the message nor cause were specified + public override string Message + { + get + { + string message = base.Message; + + if (message != null) + { + return message; + } + + if (cause != null) + { + return cause.Message; + } + + return null; + } + } + + /** + * Returns the certification path that was being validated when the + * exception was thrown. + * + * @return the CertPath that was being validated when the + * exception was thrown (or null if not specified) + */ + public PkixCertPath CertPath + { + get { return certPath; } + } + + /** + * Returns the index of the certificate in the certification path that + * caused the exception to be thrown. Note that the list of certificates in + * a CertPath is zero based. If no index has been set, -1 is + * returned. + * + * @return the index that has been set, or -1 if none has been set + */ + public int Index + { + get { return index; } + } + +// /** +// * Returns the cause of this CertPathValidatorException or +// * null if the cause is nonexistent or unknown. +// * +// * @return the cause of this throwable or null if the cause +// * is nonexistent or unknown. +// */ +// public Throwable getCause() +// { +// return cause; +// } +// +// /** +// * Returns a string describing this exception, including a description of +// * the internal (wrapped) cause if there is one. +// * +// * @return a string representation of this +// * CertPathValidatorException +// */ +// public String toString() +// { +// StringBuffer sb = new StringBuffer(); +// String s = getMessage(); +// if (s != null) +// { +// sb.append(s); +// } +// if (getIndex() >= 0) +// { +// sb.append("index in certpath: ").append(getIndex()).append('\n'); +// sb.append(getCertPath()); +// } +// return sb.toString(); +// } + + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathValidatorResult.cs b/src/core/srcbc/pkix/PkixCertPathValidatorResult.cs new file mode 100644 index 0000000..bd717b7 --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathValidatorResult.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathValidatorResult. + /// + public class PkixCertPathValidatorResult + //: ICertPathValidatorResult + { + private TrustAnchor trustAnchor; + private PkixPolicyNode policyTree; + private AsymmetricKeyParameter subjectPublicKey; + + public PkixPolicyNode PolicyTree + { + get { return this.policyTree; } + } + + public TrustAnchor TrustAnchor + { + get { return this.trustAnchor; } + } + + public AsymmetricKeyParameter SubjectPublicKey + { + get { return this.subjectPublicKey; } + } + + public PkixCertPathValidatorResult( + TrustAnchor trustAnchor, + PkixPolicyNode policyTree, + AsymmetricKeyParameter subjectPublicKey) + { + if (subjectPublicKey == null) + { + throw new NullReferenceException("subjectPublicKey must be non-null"); + } + if (trustAnchor == null) + { + throw new NullReferenceException("trustAnchor must be non-null"); + } + + this.trustAnchor = trustAnchor; + this.policyTree = policyTree; + this.subjectPublicKey = subjectPublicKey; + } + + public object Clone() + { + return new PkixCertPathValidatorResult(this.TrustAnchor, this.PolicyTree, this.SubjectPublicKey); + } + + public override String ToString() + { + StringBuilder sB = new StringBuilder(); + sB.Append("PKIXCertPathValidatorResult: [ \n"); + sB.Append(" Trust Anchor: ").Append(this.TrustAnchor).Append('\n'); + sB.Append(" Policy Tree: ").Append(this.PolicyTree).Append('\n'); + sB.Append(" Subject Public Key: ").Append(this.SubjectPublicKey).Append("\n]"); + return sB.ToString(); + } + + } +} diff --git a/src/core/srcbc/pkix/PkixCertPathValidatorUtilities.cs b/src/core/srcbc/pkix/PkixCertPathValidatorUtilities.cs new file mode 100644 index 0000000..bc0c135 --- /dev/null +++ b/src/core/srcbc/pkix/PkixCertPathValidatorUtilities.cs @@ -0,0 +1,1274 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.IsisMtt; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Extension; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixCertPathValidatorUtilities. + /// + public class PkixCertPathValidatorUtilities + { + internal static readonly string ANY_POLICY = "2.5.29.32.0"; + + internal static readonly string CRL_NUMBER = X509Extensions.CrlNumber.Id; + + /// + /// key usage bits + /// + internal static readonly int KEY_CERT_SIGN = 5; + internal static readonly int CRL_SIGN = 6; + + internal static readonly string[] crlReasons = new string[] + { + "unspecified", + "keyCompromise", + "cACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + "unknown", + "removeFromCRL", + "privilegeWithdrawn", + "aACompromise" + }; + + /// + /// Search the given Set of TrustAnchor's for one that is the + /// issuer of the given X509 certificate. + /// + /// the X509 certificate + /// a Set of TrustAnchor's + /// the TrustAnchor object if found or + /// null if not. + /// + /// @exception + internal static TrustAnchor FindTrustAnchor( + X509Certificate cert, + ISet trustAnchors) + { + IEnumerator iter = trustAnchors.GetEnumerator(); + TrustAnchor trust = null; + AsymmetricKeyParameter trustPublicKey = null; + Exception invalidKeyEx = null; + + X509CertStoreSelector certSelectX509 = new X509CertStoreSelector(); + + try + { + certSelectX509.Subject = GetIssuerPrincipal(cert); + } + catch (IOException ex) + { + throw new Exception("Cannot set subject search criteria for trust anchor.", ex); + } + + while (iter.MoveNext() && trust == null) + { + trust = (TrustAnchor) iter.Current; + if (trust.TrustedCert != null) + { + if (certSelectX509.Match(trust.TrustedCert)) + { + trustPublicKey = trust.TrustedCert.GetPublicKey(); + } + else + { + trust = null; + } + } + else if (trust.CAName != null && trust.CAPublicKey != null) + { + try + { + X509Name certIssuer = GetIssuerPrincipal(cert); + X509Name caName = new X509Name(trust.CAName); + + if (certIssuer.Equivalent(caName, true)) + { + trustPublicKey = trust.CAPublicKey; + } + else + { + trust = null; + } + } + catch (InvalidParameterException) + { + trust = null; + } + } + else + { + trust = null; + } + + if (trustPublicKey != null) + { + try + { + cert.Verify(trustPublicKey); + } + catch (Exception ex) + { + invalidKeyEx = ex; + trust = null; + } + } + } + + if (trust == null && invalidKeyEx != null) + { + throw new Exception("TrustAnchor found but certificate validation failed.", invalidKeyEx); + } + + return trust; + } + + internal static void AddAdditionalStoresFromAltNames( + X509Certificate cert, + PkixParameters pkixParams) + { + // if in the IssuerAltName extension an URI + // is given, add an additinal X.509 store + if (cert.GetIssuerAlternativeNames() != null) + { + IEnumerator it = cert.GetIssuerAlternativeNames().GetEnumerator(); + while (it.MoveNext()) + { + // look for URI + IList list = (IList)it.Current; + //if (list[0].Equals(new Integer(GeneralName.UniformResourceIdentifier))) + if (list[0].Equals(GeneralName.UniformResourceIdentifier)) + { + // found + string temp = (string)list[1]; + PkixCertPathValidatorUtilities.AddAdditionalStoreFromLocation(temp, pkixParams); + } + } + } + } + + internal static DateTime GetValidDate(PkixParameters paramsPKIX) + { + DateTimeObject validDate = paramsPKIX.Date; + + if (validDate == null) + return DateTime.UtcNow; + + return validDate.Value; + } + + /// + /// Returns the issuer of an attribute certificate or certificate. + /// + /// The attribute certificate or certificate. + /// The issuer as X500Principal. + internal static X509Name GetIssuerPrincipal( + object cert) + { + if (cert is X509Certificate) + { + return ((X509Certificate)cert).IssuerDN; + } + else + { + return ((IX509AttributeCertificate)cert).Issuer.GetPrincipals()[0]; + } + } + + internal static bool IsSelfIssued( + X509Certificate cert) + { + return cert.SubjectDN.Equivalent(cert.IssuerDN, true); + } + + internal static AlgorithmIdentifier GetAlgorithmIdentifier( + AsymmetricKeyParameter key) + { + try + { + SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(key); + + return info.AlgorithmID; + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Subject public key cannot be decoded.", e); + } + } + + /// + /// crl checking + /// Return a Collection of all CRLs found in the X509Store's that are + /// matching the crlSelect criteriums. + /// + /// a {@link X509CRLStoreSelector} object that will be used + /// to select the CRLs + /// a List containing only {@link org.bouncycastle.x509.X509Store + /// X509Store} objects. These are used to search for CRLs + /// a Collection of all found {@link X509CRL X509CRL} objects. May be + /// empty but never null. + /// + internal static ICollection FindCrls( + X509CrlStoreSelector crlSelect, + IList crlStores) + { + ISet crls = new HashSet(); + + Exception lastException = null; + bool foundValidStore = false; + + foreach (IX509Store store in crlStores) + { + try + { +// crls.AddAll(store.GetMatches(crlSelect)); + foreach (X509Crl crl in store.GetMatches(crlSelect)) + { + crls.Add(crl); + } + + foundValidStore = true; + } + catch (Exception e) + { + lastException = new Exception( + "Exception searching in X.509 CRL store.", e); + } + } + + if (!foundValidStore && lastException != null) + { + throw lastException; + } + + return crls; + } + + internal static bool IsAnyPolicy( + ISet policySet) + { + return policySet == null || policySet.Contains(ANY_POLICY) || policySet.Count == 0; + } + + internal static void AddAdditionalStoreFromLocation( + string location, + PkixParameters pkixParams) + { + if (pkixParams.IsAdditionalLocationsEnabled) + { + try + { + if (location.StartsWith("ldap://")) + { + // ldap://directory.d-trust.net/CN=D-TRUST + // Qualified CA 2003 1:PN,O=D-Trust GmbH,C=DE + // skip "ldap://" + location = location.Substring(7); + // after first / baseDN starts + string url, baseDN; + int slashPos = location.IndexOf('/'); + if (slashPos != -1) + { + url = "ldap://" + location.Substring(0, slashPos); + baseDN = location.Substring(slashPos); + } + else + { + url = "ldap://" + location; + baseDN = null; + } + + throw Platform.CreateNotImplementedException("LDAP cert/CRL stores"); + + // use all purpose parameters + //X509LDAPCertStoreParameters ldapParams = new X509LDAPCertStoreParameters.Builder( + // url, baseDN).build(); + //pkixParams.addAddionalStore(X509Store.getInstance( + // "CERTIFICATE/LDAP", ldapParams, "BC")); + //pkixParams.addAddionalStore(X509Store.getInstance( + // "CRL/LDAP", ldapParams, "BC")); + //pkixParams.addAddionalStore(X509Store.getInstance( + // "ATTRIBUTECERTIFICATE/LDAP", ldapParams, "BC")); + //pkixParams.addAddionalStore(X509Store.getInstance( + // "CERTIFICATEPAIR/LDAP", ldapParams, "BC")); + } + } + catch (Exception) + { + // cannot happen + throw new Exception("Exception adding X.509 stores."); + } + } + } + + private static BigInteger GetSerialNumber( + object cert) + { + if (cert is X509Certificate) + { + return ((X509Certificate)cert).SerialNumber; + } + else + { + return ((X509V2AttributeCertificate)cert).SerialNumber; + } + } + + // + // policy checking + // + + internal static ISet GetQualifierSet(Asn1Sequence qualifiers) + { + ISet pq = new HashSet(); + + if (qualifiers == null) + { + return pq; + } + + foreach (Asn1Encodable ae in qualifiers) + { + try + { +// pq.Add(PolicyQualifierInfo.GetInstance(Asn1Object.FromByteArray(ae.GetEncoded()))); + pq.Add(PolicyQualifierInfo.GetInstance(ae.ToAsn1Object())); + } + catch (IOException ex) + { + throw new PkixCertPathValidatorException("Policy qualifier info cannot be decoded.", ex); + } + } + + return pq; + } + + internal static PkixPolicyNode RemovePolicyNode( + PkixPolicyNode validPolicyTree, + IList[] policyNodes, + PkixPolicyNode _node) + { + PkixPolicyNode _parent = (PkixPolicyNode)_node.Parent; + + if (validPolicyTree == null) + { + return null; + } + + if (_parent == null) + { + for (int j = 0; j < policyNodes.Length; j++) + { + policyNodes[j] = new ArrayList(); + } + + return null; + } + else + { + _parent.RemoveChild(_node); + RemovePolicyNodeRecurse(policyNodes, _node); + + return validPolicyTree; + } + } + + private static void RemovePolicyNodeRecurse(IList[] policyNodes, PkixPolicyNode _node) + { + policyNodes[_node.Depth].Remove(_node); + + if (_node.HasChildren) + { + foreach (PkixPolicyNode _child in _node.Children) + { + RemovePolicyNodeRecurse(policyNodes, _child); + } + } + } + + internal static void PrepareNextCertB1( + int i, + IList[] policyNodes, + string id_p, + IDictionary m_idp, + X509Certificate cert) + { + bool idp_found = false; + IEnumerator nodes_i = policyNodes[i].GetEnumerator(); + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (node.ValidPolicy.Equals(id_p)) + { + idp_found = true; + node.ExpectedPolicies = (ISet)m_idp[id_p]; + break; + } + } + + if (!idp_found) + { + nodes_i = policyNodes[i].GetEnumerator(); + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (ANY_POLICY.Equals(node.ValidPolicy)) + { + ISet pq = null; + Asn1Sequence policies = null; + try + { + policies = DerSequence.GetInstance(GetExtensionValue(cert, X509Extensions.CertificatePolicies)); + } + catch (Exception e) + { + throw new Exception("Certificate policies cannot be decoded.", e); + } + + IEnumerator enm = policies.GetEnumerator(); + while (enm.MoveNext()) + { + PolicyInformation pinfo = null; + + try + { + pinfo = PolicyInformation.GetInstance(enm.Current); + } + catch (Exception ex) + { + throw new Exception("Policy information cannot be decoded.", ex); + } + + if (ANY_POLICY.Equals(pinfo.PolicyIdentifier.Id)) + { + try + { + pq = GetQualifierSet(pinfo.PolicyQualifiers); + } + catch (PkixCertPathValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Policy qualifier info set could not be built.", ex); + } + break; + } + } + bool ci = false; + ISet critExtOids = cert.GetCriticalExtensionOids(); + if (critExtOids != null) + { + ci = critExtOids.Contains(X509Extensions.CertificatePolicies.Id); + } + + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + if (ANY_POLICY.Equals(p_node.ValidPolicy)) + { + PkixPolicyNode c_node = new PkixPolicyNode( + new ArrayList(), i, + (ISet)m_idp[id_p], + p_node, pq, id_p, ci); + p_node.AddChild(c_node); + policyNodes[i].Add(c_node); + } + break; + } + } + } + } + + internal static PkixPolicyNode PrepareNextCertB2( + int i, + IList[] policyNodes, + string id_p, + PkixPolicyNode validPolicyTree) + { + int pos = 0; + + // Copy to avoid RemoveAt calls interfering with enumeration + foreach (PkixPolicyNode node in new ArrayList(policyNodes[i])) + { + if (node.ValidPolicy.Equals(id_p)) + { + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + p_node.RemoveChild(node); + + // Removal of element at current iterator position not supported in C# + //nodes_i.remove(); + policyNodes[i].RemoveAt(pos); + + for (int k = (i - 1); k >= 0; k--) + { + IList nodes = policyNodes[k]; + for (int l = 0; l < nodes.Count; l++) + { + PkixPolicyNode node2 = (PkixPolicyNode)nodes[l]; + if (!node2.HasChildren) + { + validPolicyTree = RemovePolicyNode(validPolicyTree, policyNodes, node2); + if (validPolicyTree == null) + break; + } + } + } + } + else + { + ++pos; + } + } + return validPolicyTree; + } + + internal static void GetCertStatus( + DateTime validDate, + X509Crl crl, + Object cert, + CertStatus certStatus) + { + X509Crl bcCRL = null; + + try + { + bcCRL = new X509Crl(CertificateList.GetInstance((Asn1Sequence)Asn1Sequence.FromByteArray(crl.GetEncoded()))); + } + catch (Exception exception) + { + throw new Exception("Bouncy Castle X509Crl could not be created.", exception); + } + + X509CrlEntry crl_entry = (X509CrlEntry)bcCRL.GetRevokedCertificate(GetSerialNumber(cert)); + + if (crl_entry == null) + return; + + X509Name issuer = GetIssuerPrincipal(cert); + + if (issuer.Equivalent(crl_entry.GetCertificateIssuer(), true) + || issuer.Equivalent(crl.IssuerDN, true)) + { + DerEnumerated reasonCode = null; + if (crl_entry.HasExtensions) + { + try + { + reasonCode = DerEnumerated.GetInstance( + GetExtensionValue(crl_entry, X509Extensions.ReasonCode)); + } + catch (Exception e) + { + new Exception( + "Reason code CRL entry extension could not be decoded.", + e); + } + } + + // for reason keyCompromise, caCompromise, aACompromise or + // unspecified + if (!(validDate.Ticks < crl_entry.RevocationDate.Ticks) + || reasonCode == null + || reasonCode.Value.TestBit(0) + || reasonCode.Value.TestBit(1) + || reasonCode.Value.TestBit(2) + || reasonCode.Value.TestBit(8)) + { + if (reasonCode != null) // (i) or (j) (1) + { + certStatus.Status = reasonCode.Value.SignValue; + } + else // (i) or (j) (2) + { + certStatus.Status = CrlReason.Unspecified; + } + certStatus.RevocationDate = new DateTimeObject(crl_entry.RevocationDate); + } + } + } + + /** + * Return the next working key inheriting DSA parameters if necessary. + *

+ * This methods inherits DSA parameters from the indexed certificate or + * previous certificates in the certificate chain to the returned + * PublicKey. The list is searched upwards, meaning the end + * certificate is at position 0 and previous certificates are following. + *

+ *

+ * If the indexed certificate does not contain a DSA key this method simply + * returns the public key. If the DSA key already contains DSA parameters + * the key is also only returned. + *

+ * + * @param certs The certification path. + * @param index The index of the certificate which contains the public key + * which should be extended with DSA parameters. + * @return The public key of the certificate in list position + * index extended with DSA parameters if applicable. + * @throws Exception if DSA parameters cannot be inherited. + */ + internal static AsymmetricKeyParameter GetNextWorkingKey( + IList certs, + int index) + { + //Only X509Certificate + X509Certificate cert = (X509Certificate)certs[index]; + + AsymmetricKeyParameter pubKey = cert.GetPublicKey(); + + if (!(pubKey is DsaPublicKeyParameters)) + return pubKey; + + DsaPublicKeyParameters dsaPubKey = (DsaPublicKeyParameters)pubKey; + + if (dsaPubKey.Parameters != null) + return dsaPubKey; + + for (int i = index + 1; i < certs.Count; i++) + { + X509Certificate parentCert = (X509Certificate)certs[i]; + pubKey = parentCert.GetPublicKey(); + + if (!(pubKey is DsaPublicKeyParameters)) + { + throw new PkixCertPathValidatorException( + "DSA parameters cannot be inherited from previous certificate."); + } + + DsaPublicKeyParameters prevDSAPubKey = (DsaPublicKeyParameters)pubKey; + + if (prevDSAPubKey.Parameters == null) + continue; + + DsaParameters dsaParams = prevDSAPubKey.Parameters; + + try + { + return new DsaPublicKeyParameters(dsaPubKey.Y, dsaParams); + } + catch (Exception exception) + { + throw new Exception(exception.Message); + } + } + + throw new PkixCertPathValidatorException("DSA parameters cannot be inherited from previous certificate."); + } + + internal static DateTime GetValidCertDateFromValidityModel( + PkixParameters paramsPkix, + PkixCertPath certPath, + int index) + { + if (paramsPkix.ValidityModel != PkixParameters.ChainValidityModel) + { + return GetValidDate(paramsPkix); + } + + // if end cert use given signing/encryption/... time + if (index <= 0) + { + return PkixCertPathValidatorUtilities.GetValidDate(paramsPkix); + // else use time when previous cert was created + } + + if (index - 1 == 0) + { + DerGeneralizedTime dateOfCertgen = null; + try + { + X509Certificate cert = (X509Certificate)certPath.Certificates[index - 1]; + Asn1OctetString extVal = cert.GetExtensionValue( + IsisMttObjectIdentifiers.IdIsisMttATDateOfCertGen); + dateOfCertgen = DerGeneralizedTime.GetInstance(extVal); + } + catch (ArgumentException) + { + throw new Exception( + "Date of cert gen extension could not be read."); + } + if (dateOfCertgen != null) + { + try + { + return dateOfCertgen.ToDateTime(); + } + catch (ArgumentException e) + { + throw new Exception( + "Date from date of cert gen extension could not be parsed.", + e); + } + } + } + + return ((X509Certificate)certPath.Certificates[index - 1]).NotBefore; + } + + /// + /// Return a Collection of all certificates or attribute certificates found + /// in the X509Store's that are matching the certSelect criteriums. + /// + /// a {@link Selector} object that will be used to select + /// the certificates + /// a List containing only X509Store objects. These + /// are used to search for certificates. + /// a Collection of all found or + /// org.bouncycastle.x509.X509AttributeCertificate objects. + /// May be empty but never null. + /// + internal static ICollection FindCertificates( + X509CertStoreSelector certSelect, + IList certStores) + { + ISet certs = new HashSet(); + + foreach (IX509Store certStore in certStores) + { + try + { +// certs.AddAll(certStore.GetMatches(certSelect)); + foreach (X509Certificate c in certStore.GetMatches(certSelect)) + { + certs.Add(c); + } + } + catch (Exception e) + { + throw new Exception("Problem while picking certificates from X.509 store.", e); + } + } + + return certs; + } + + /** + * Add the CRL issuers from the cRLIssuer field of the distribution point or + * from the certificate if not given to the issuer criterion of the + * selector. + *

+ * The issuerPrincipals are a collection with a single + * X500Principal for X509Certificates. For + * {@link X509AttributeCertificate}s the issuer may contain more than one + * X500Principal. + *

+ * + * @param dp The distribution point. + * @param issuerPrincipals The issuers of the certificate or attribute + * certificate which contains the distribution point. + * @param selector The CRL selector. + * @param pkixParams The PKIX parameters containing the cert stores. + * @throws Exception if an exception occurs while processing. + * @throws ClassCastException if issuerPrincipals does not + * contain only X500Principals. + */ + internal static void GetCrlIssuersFromDistributionPoint( + DistributionPoint dp, + ICollection issuerPrincipals, + X509CrlStoreSelector selector, + PkixParameters pkixParams) + { + IList issuers = new ArrayList(); + // indirect CRL + if (dp.CrlIssuer != null) + { + GeneralName[] genNames = dp.CrlIssuer.GetNames(); + // look for a DN + for (int j = 0; j < genNames.Length; j++) + { + if (genNames[j].TagNo == GeneralName.DirectoryName) + { + try + { + issuers.Add(X509Name.GetInstance(genNames[j].Name.ToAsn1Object())); + } + catch (IOException e) + { + throw new Exception( + "CRL issuer information from distribution point cannot be decoded.", + e); + } + } + } + } + else + { + /* + * certificate issuer is CRL issuer, distributionPoint field MUST be + * present. + */ + if (dp.DistributionPointName == null) + { + throw new Exception( + "CRL issuer is omitted from distribution point but no distributionPoint field present."); + } + + // add and check issuer principals + for (IEnumerator it = issuerPrincipals.GetEnumerator(); it.MoveNext(); ) + { + issuers.Add((X509Name)it.Current); + } + } + // TODO: is not found although this should correctly add the rel name. selector of Sun is buggy here or PKI test case is invalid + // distributionPoint + // if (dp.getDistributionPoint() != null) + // { + // // look for nameRelativeToCRLIssuer + // if (dp.getDistributionPoint().getType() == DistributionPointName.NAME_RELATIVE_TO_CRL_ISSUER) + // { + // // append fragment to issuer, only one + // // issuer can be there, if this is given + // if (issuers.size() != 1) + // { + // throw new AnnotatedException( + // "nameRelativeToCRLIssuer field is given but more than one CRL issuer is given."); + // } + // DEREncodable relName = dp.getDistributionPoint().getName(); + // Iterator it = issuers.iterator(); + // List issuersTemp = new ArrayList(issuers.size()); + // while (it.hasNext()) + // { + // Enumeration e = null; + // try + // { + // e = ASN1Sequence.getInstance( + // new ASN1InputStream(((X500Principal) it.next()) + // .getEncoded()).readObject()).getObjects(); + // } + // catch (IOException ex) + // { + // throw new AnnotatedException( + // "Cannot decode CRL issuer information.", ex); + // } + // ASN1EncodableVector v = new ASN1EncodableVector(); + // while (e.hasMoreElements()) + // { + // v.add((DEREncodable) e.nextElement()); + // } + // v.add(relName); + // issuersTemp.add(new X500Principal(new DERSequence(v) + // .getDEREncoded())); + // } + // issuers.clear(); + // issuers.addAll(issuersTemp); + // } + // } + + selector.Issuers = issuers; + } + + /** + * Fetches complete CRLs according to RFC 3280. + * + * @param dp The distribution point for which the complete CRL + * @param cert The X509Certificate or + * {@link org.bouncycastle.x509.X509AttributeCertificate} for + * which the CRL should be searched. + * @param currentDate The date for which the delta CRLs must be valid. + * @param paramsPKIX The extended PKIX parameters. + * @return A Set of X509CRLs with complete + * CRLs. + * @throws Exception if an exception occurs while picking the CRLs + * or no CRLs are found. + */ + internal static ISet GetCompleteCrls( + DistributionPoint dp, + object cert, + DateTime currentDate, + PkixParameters paramsPKIX) + { + X509CrlStoreSelector crlselect = new X509CrlStoreSelector(); + try + { + ISet issuers = new HashSet(); + if (cert is X509V2AttributeCertificate) + { + issuers.Add(((X509V2AttributeCertificate)cert) + .Issuer.GetPrincipals()[0]); + } + else + { + issuers.Add(GetIssuerPrincipal(cert)); + } + PkixCertPathValidatorUtilities.GetCrlIssuersFromDistributionPoint(dp, issuers, crlselect, paramsPKIX); + } + catch (Exception e) + { + new Exception("Could not get issuer information from distribution point.", e); + } + + if (cert is X509Certificate) + { + crlselect.CertificateChecking = (X509Certificate)cert; + } + else if (cert is X509V2AttributeCertificate) + { + crlselect.AttrCertChecking = (IX509AttributeCertificate)cert; + } + + if (paramsPKIX.Date != null) + { + crlselect.DateAndTime = paramsPKIX.Date; + } + else + { + crlselect.DateAndTime = new DateTimeObject(currentDate); + } + + crlselect.CompleteCrlEnabled = true; + + ISet crls = new HashSet(); + try + { + crls.AddAll(PkixCertPathValidatorUtilities.FindCrls(crlselect, paramsPKIX.GetStores())); + crls.AddAll(PkixCertPathValidatorUtilities.FindCrls(crlselect, paramsPKIX.GetAdditionalStores())); + crls.AddAll(PkixCertPathValidatorUtilities.FindCrls(crlselect, paramsPKIX.GetX509Stores())); + } + catch (Exception e) + { + throw new Exception("Could not search for CRLs.", e); + } + + if (crls.IsEmpty) + throw new Exception("No CRLs found."); + + return crls; + } + + /** + * Fetches delta CRLs according to RFC 3280 section 5.2.4. + * + * @param currentDate The date for which the delta CRLs must be valid. + * @param paramsPKIX The extended PKIX parameters. + * @param completeCRL The complete CRL the delta CRL is for. + * @return A Set of X509CRLs with delta CRLs. + * @throws Exception if an exception occurs while picking the delta + * CRLs. + */ + internal static ISet GetDeltaCrls( + DateTime currentDate, + PkixParameters paramsPKIX, + X509Crl completeCRL) + { + X509CrlStoreSelector deltaSelect = new X509CrlStoreSelector(); + + if (paramsPKIX.Date != null) + { + deltaSelect.DateAndTime = paramsPKIX.Date; + } + else + { + deltaSelect.DateAndTime = new DateTimeObject(currentDate); + } + + // 5.2.4 (a) + try + { + IList deltaSelectIssuer = new ArrayList(); + deltaSelectIssuer.Add(completeCRL.IssuerDN); + deltaSelect.Issuers = deltaSelectIssuer; + } + catch (IOException e) + { + new Exception("Cannot extract issuer from CRL.", e); + } + + BigInteger completeCRLNumber = null; + try + { + Asn1Object asn1Object = GetExtensionValue(completeCRL, X509Extensions.CrlNumber); + if (asn1Object != null) + { + completeCRLNumber = CrlNumber.GetInstance(asn1Object).PositiveValue; + } + } + catch (Exception e) + { + throw new Exception( + "CRL number extension could not be extracted from CRL.", e); + } + + // 5.2.4 (b) + byte[] idp = null; + + try + { + Asn1Object obj = GetExtensionValue(completeCRL, X509Extensions.IssuingDistributionPoint); + if (obj != null) + { + idp = obj.GetDerEncoded(); + } + } + catch (Exception e) + { + throw new Exception( + "Issuing distribution point extension value could not be read.", + e); + } + + // 5.2.4 (d) + + deltaSelect.MinCrlNumber = (completeCRLNumber == null) + ? null + : completeCRLNumber.Add(BigInteger.One); + + deltaSelect.IssuingDistributionPoint = idp; + deltaSelect.IssuingDistributionPointEnabled = true; + + // 5.2.4 (c) + deltaSelect.MaxBaseCrlNumber = completeCRLNumber; + + ISet temp = new HashSet(); + // find delta CRLs + try + { + temp.AddAll(PkixCertPathValidatorUtilities.FindCrls(deltaSelect, paramsPKIX.GetAdditionalStores())); + temp.AddAll(PkixCertPathValidatorUtilities.FindCrls(deltaSelect, paramsPKIX.GetStores())); + temp.AddAll(PkixCertPathValidatorUtilities.FindCrls(deltaSelect, paramsPKIX.GetX509Stores())); + } + catch (Exception e) + { + throw new Exception("Could not search for delta CRLs.", e); + } + + ISet result = new HashSet(); + + foreach (X509Crl crl in temp) + { + if (isDeltaCrl(crl)) + { + result.Add(crl); + } + } + + return result; + } + + private static bool isDeltaCrl( + X509Crl crl) + { + ISet critical = crl.GetCriticalExtensionOids(); + + return critical.Contains(X509Extensions.DeltaCrlIndicator.Id); + } + + internal static ICollection FindCertificates( + X509AttrCertStoreSelector certSelect, + IList certStores) + { + ISet certs = new HashSet(); + + foreach (IX509Store certStore in certStores) + { + try + { +// certs.AddAll(certStore.GetMatches(certSelect)); + foreach (X509V2AttributeCertificate ac in certStore.GetMatches(certSelect)) + { + certs.Add(ac); + } + } + catch (Exception e) + { + throw new Exception( + "Problem while picking certificates from X.509 store.", e); + } + } + + return certs; + } + + internal static void AddAdditionalStoresFromCrlDistributionPoint( + CrlDistPoint crldp, + PkixParameters pkixParams) + { + if (crldp != null) + { + DistributionPoint[] dps = null; + try + { + dps = crldp.GetDistributionPoints(); + } + catch (Exception e) + { + throw new Exception( + "Distribution points could not be read.", e); + } + for (int i = 0; i < dps.Length; i++) + { + DistributionPointName dpn = dps[i].DistributionPointName; + // look for URIs in fullName + if (dpn != null) + { + if (dpn.PointType == DistributionPointName.FullName) + { + GeneralName[] genNames = GeneralNames.GetInstance( + dpn.Name).GetNames(); + // look for an URI + for (int j = 0; j < genNames.Length; j++) + { + if (genNames[j].TagNo == GeneralName.UniformResourceIdentifier) + { + string location = DerIA5String.GetInstance( + genNames[j].Name).GetString(); + PkixCertPathValidatorUtilities.AddAdditionalStoreFromLocation( + location, pkixParams); + } + } + } + } + } + } + } + + internal static bool ProcessCertD1i( + int index, + IList[] policyNodes, + DerObjectIdentifier pOid, + ISet pq) + { + IList policyNodeVec = policyNodes[index - 1]; + + for (int j = 0; j < policyNodeVec.Count; j++) + { + PkixPolicyNode node = (PkixPolicyNode)policyNodeVec[j]; + ISet expectedPolicies = node.ExpectedPolicies; + + if (expectedPolicies.Contains(pOid.Id)) + { + ISet childExpectedPolicies = new HashSet(); + childExpectedPolicies.Add(pOid.Id); + + PkixPolicyNode child = new PkixPolicyNode(new ArrayList(), + index, + childExpectedPolicies, + node, + pq, + pOid.Id, + false); + node.AddChild(child); + policyNodes[index].Add(child); + + return true; + } + } + + return false; + } + + internal static void ProcessCertD1ii( + int index, + IList[] policyNodes, + DerObjectIdentifier _poid, + ISet _pq) + { + IList policyNodeVec = policyNodes[index - 1]; + + for (int j = 0; j < policyNodeVec.Count; j++) + { + PkixPolicyNode _node = (PkixPolicyNode)policyNodeVec[j]; + + if (ANY_POLICY.Equals(_node.ValidPolicy)) + { + ISet _childExpectedPolicies = new HashSet(); + _childExpectedPolicies.Add(_poid.Id); + + PkixPolicyNode _child = new PkixPolicyNode(new ArrayList(), + index, + _childExpectedPolicies, + _node, + _pq, + _poid.Id, + false); + _node.AddChild(_child); + policyNodes[index].Add(_child); + return; + } + } + } + + /** + * Find the issuer certificates of a given certificate. + * + * @param cert + * The certificate for which an issuer should be found. + * @param pkixParams + * @return A Collection object containing the issuer + * X509Certificates. Never null. + * + * @exception Exception + * if an error occurs. + */ + internal static ICollection FindIssuerCerts( + X509Certificate cert, + PkixBuilderParameters pkixParams) + { + X509CertStoreSelector certSelect = new X509CertStoreSelector(); + ISet certs = new HashSet(); + try + { + certSelect.Subject = cert.IssuerDN; + } + catch (IOException ex) + { + throw new Exception( + "Subject criteria for certificate selector to find issuer certificate could not be set.", ex); + } + + try + { + ArrayList matches = new ArrayList(); + + matches.AddRange(PkixCertPathValidatorUtilities.FindCertificates(certSelect, pkixParams.GetX509Stores())); + matches.AddRange(PkixCertPathValidatorUtilities.FindCertificates(certSelect, pkixParams.GetStores())); + matches.AddRange(PkixCertPathValidatorUtilities.FindCertificates(certSelect, pkixParams.GetAdditionalStores())); + + foreach (X509Certificate issuer in matches) + { + certs.Add(issuer); + } + } + catch (Exception e) + { + throw new Exception("Issuer certificate cannot be searched.", e); + } + + return certs; + } + + /// + /// Extract the value of the given extension, if it exists. + /// + /// The extension object. + /// The object identifier to obtain. + /// Asn1Object + /// if the extension cannot be read. + internal static Asn1Object GetExtensionValue( + IX509Extension ext, + DerObjectIdentifier oid) + { + Asn1OctetString bytes = ext.GetExtensionValue(oid); + + if (bytes == null) + return null; + + return X509ExtensionUtilities.FromExtensionValue(bytes); + } + } +} diff --git a/src/core/srcbc/pkix/PkixNameConstraintValidator.cs b/src/core/srcbc/pkix/PkixNameConstraintValidator.cs new file mode 100644 index 0000000..04f7b6e --- /dev/null +++ b/src/core/srcbc/pkix/PkixNameConstraintValidator.cs @@ -0,0 +1,1935 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixNameConstraintValidator + { + private ISet excludedSubtreesDN = new HashSet(); + + private ISet excludedSubtreesDNS = new HashSet(); + + private ISet excludedSubtreesEmail = new HashSet(); + + private ISet excludedSubtreesURI = new HashSet(); + + private ISet excludedSubtreesIP = new HashSet(); + + private ISet permittedSubtreesDN; + + private ISet permittedSubtreesDNS; + + private ISet permittedSubtreesEmail; + + private ISet permittedSubtreesURI; + + private ISet permittedSubtreesIP; + + public PkixNameConstraintValidator() + { + } + + private static bool WithinDNSubtree( + Asn1Sequence dns, + Asn1Sequence subtree) + { + if (subtree.Count < 1) + { + return false; + } + + if (subtree.Count > dns.Count) + { + return false; + } + + for (int j = subtree.Count - 1; j >= 0; j--) + { + if (!(subtree[j].Equals(dns[j]))) + { + return false; + } + } + + return true; + } + + public void CheckPermittedDN(Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + CheckPermittedDN(permittedSubtreesDN, dns); + } + + public void CheckExcludedDN(Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + CheckExcludedDN(excludedSubtreesDN, dns); + } + + private void CheckPermittedDN(ISet permitted, Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + if ((permitted.Count == 0) && dns.Count == 0) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dns, subtree)) + { + return; + } + } + + throw new PkixNameConstraintValidatorException( + "Subject distinguished name is not from a permitted subtree"); + } + + private void CheckExcludedDN(ISet excluded, Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dns, subtree)) + { + throw new PkixNameConstraintValidatorException( + "Subject distinguished name is from an excluded subtree"); + } + } + } + + private ISet IntersectDN(ISet permitted, ISet dns) + { + ISet intersect = new HashSet(); + for (IEnumerator it = dns.GetEnumerator(); it.MoveNext(); ) + { + Asn1Sequence dn = Asn1Sequence.GetInstance(((GeneralSubtree)it + .Current).Base.Name.ToAsn1Object()); + if (permitted == null) + { + if (dn != null) + { + intersect.Add(dn); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)_iter.Current; + + if (WithinDNSubtree(dn, subtree)) + { + intersect.Add(dn); + } + else if (WithinDNSubtree(subtree, dn)) + { + intersect.Add(subtree); + } + } + } + } + return intersect; + } + + private ISet UnionDN(ISet excluded, Asn1Sequence dn) + { + if (excluded.IsEmpty) + { + if (dn == null) + { + return excluded; + } + excluded.Add(dn); + + return excluded; + } + else + { + ISet intersect = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dn, subtree)) + { + intersect.Add(subtree); + } + else if (WithinDNSubtree(subtree, dn)) + { + intersect.Add(dn); + } + else + { + intersect.Add(subtree); + intersect.Add(dn); + } + } + + return intersect; + } + } + + private ISet IntersectEmail(ISet permitted, ISet emails) + { + ISet intersect = new HashSet(); + for (IEnumerator it = emails.GetEnumerator(); it.MoveNext(); ) + { + String email = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + + if (permitted == null) + { + if (email != null) + { + intersect.Add(email); + } + } + else + { + IEnumerator it2 = permitted.GetEnumerator(); + while (it2.MoveNext()) + { + String _permitted = (String)it2.Current; + + intersectEmail(email, _permitted, intersect); + } + } + } + return intersect; + } + + private ISet UnionEmail(ISet excluded, String email) + { + if (excluded.IsEmpty) + { + if (email == null) + { + return excluded; + } + excluded.Add(email); + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + String _excluded = (String)it.Current; + + unionEmail(_excluded, email, union); + } + + return union; + } + } + + /** + * Returns the intersection of the permitted IP ranges in + * permitted with ip. + * + * @param permitted A Set of permitted IP addresses with + * their subnet mask as byte arrays. + * @param ips The IP address with its subnet mask. + * @return The Set of permitted IP ranges intersected with + * ip. + */ + private ISet IntersectIP(ISet permitted, ISet ips) + { + ISet intersect = new HashSet(); + for (IEnumerator it = ips.GetEnumerator(); it.MoveNext(); ) + { + byte[] ip = Asn1OctetString.GetInstance( + ((GeneralSubtree)it.Current).Base.Name).GetOctets(); + if (permitted == null) + { + if (ip != null) + { + intersect.Add(ip); + } + } + else + { + IEnumerator it2 = permitted.GetEnumerator(); + while (it2.MoveNext()) + { + byte[] _permitted = (byte[])it2.Current; + intersect.AddAll(IntersectIPRange(_permitted, ip)); + } + } + } + return intersect; + } + + /** + * Returns the union of the excluded IP ranges in excluded + * with ip. + * + * @param excluded A Set of excluded IP addresses with their + * subnet mask as byte arrays. + * @param ip The IP address with its subnet mask. + * @return The Set of excluded IP ranges unified with + * ip as byte arrays. + */ + private ISet UnionIP(ISet excluded, byte[] ip) + { + if (excluded.IsEmpty) + { + if (ip == null) + { + return excluded; + } + excluded.Add(ip); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + byte[] _excluded = (byte[])it.Current; + union.AddAll(UnionIPRange(_excluded, ip)); + } + + return union; + } + } + + /** + * Calculates the union if two IP ranges. + * + * @param ipWithSubmask1 The first IP address with its subnet mask. + * @param ipWithSubmask2 The second IP address with its subnet mask. + * @return A Set with the union of both addresses. + */ + private ISet UnionIPRange(byte[] ipWithSubmask1, byte[] ipWithSubmask2) + { + ISet set = new HashSet(); + + // difficult, adding always all IPs is not wrong + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(ipWithSubmask1, ipWithSubmask2)) + { + set.Add(ipWithSubmask1); + } + else + { + set.Add(ipWithSubmask1); + set.Add(ipWithSubmask2); + } + return set; + } + + /** + * Calculates the interesction if two IP ranges. + * + * @param ipWithSubmask1 The first IP address with its subnet mask. + * @param ipWithSubmask2 The second IP address with its subnet mask. + * @return A Set with the single IP address with its subnet + * mask as a byte array or an empty Set. + */ + private ISet IntersectIPRange(byte[] ipWithSubmask1, byte[] ipWithSubmask2) + { + if (ipWithSubmask1.Length != ipWithSubmask2.Length) + { + //Collections.EMPTY_SET; + return new HashSet(); + } + + byte[][] temp = ExtractIPsAndSubnetMasks(ipWithSubmask1, ipWithSubmask2); + byte[] ip1 = temp[0]; + byte[] subnetmask1 = temp[1]; + byte[] ip2 = temp[2]; + byte[] subnetmask2 = temp[3]; + + byte[][] minMax = MinMaxIPs(ip1, subnetmask1, ip2, subnetmask2); + byte[] min; + byte[] max; + max = Min(minMax[1], minMax[3]); + min = Max(minMax[0], minMax[2]); + + // minimum IP address must be bigger than max + if (CompareTo(min, max) == 1) + { + //return Collections.EMPTY_SET; + return new HashSet(); + } + // OR keeps all significant bits + byte[] ip = Or(minMax[0], minMax[2]); + byte[] subnetmask = Or(subnetmask1, subnetmask2); + + //return new HashSet( ICollectionsingleton(IpWithSubnetMask(ip, subnetmask)); + ISet hs = new HashSet(); + hs.Add(IpWithSubnetMask(ip, subnetmask)); + + return hs; + } + + /** + * Concatenates the IP address with its subnet mask. + * + * @param ip The IP address. + * @param subnetMask Its subnet mask. + * @return The concatenated IP address with its subnet mask. + */ + private byte[] IpWithSubnetMask(byte[] ip, byte[] subnetMask) + { + int ipLength = ip.Length; + byte[] temp = new byte[ipLength * 2]; + Array.Copy(ip, 0, temp, 0, ipLength); + Array.Copy(subnetMask, 0, temp, ipLength, ipLength); + return temp; + } + + /** + * Splits the IP addresses and their subnet mask. + * + * @param ipWithSubmask1 The first IP address with the subnet mask. + * @param ipWithSubmask2 The second IP address with the subnet mask. + * @return An array with two elements. Each element contains the IP address + * and the subnet mask in this order. + */ + private byte[][] ExtractIPsAndSubnetMasks( + byte[] ipWithSubmask1, + byte[] ipWithSubmask2) + { + int ipLength = ipWithSubmask1.Length / 2; + byte[] ip1 = new byte[ipLength]; + byte[] subnetmask1 = new byte[ipLength]; + Array.Copy(ipWithSubmask1, 0, ip1, 0, ipLength); + Array.Copy(ipWithSubmask1, ipLength, subnetmask1, 0, ipLength); + + byte[] ip2 = new byte[ipLength]; + byte[] subnetmask2 = new byte[ipLength]; + Array.Copy(ipWithSubmask2, 0, ip2, 0, ipLength); + Array.Copy(ipWithSubmask2, ipLength, subnetmask2, 0, ipLength); + return new byte[][] + {ip1, subnetmask1, ip2, subnetmask2}; + } + + /** + * Based on the two IP addresses and their subnet masks the IP range is + * computed for each IP address - subnet mask pair and returned as the + * minimum IP address and the maximum address of the range. + * + * @param ip1 The first IP address. + * @param subnetmask1 The subnet mask of the first IP address. + * @param ip2 The second IP address. + * @param subnetmask2 The subnet mask of the second IP address. + * @return A array with two elements. The first/second element contains the + * min and max IP address of the first/second IP address and its + * subnet mask. + */ + private byte[][] MinMaxIPs( + byte[] ip1, + byte[] subnetmask1, + byte[] ip2, + byte[] subnetmask2) + { + int ipLength = ip1.Length; + byte[] min1 = new byte[ipLength]; + byte[] max1 = new byte[ipLength]; + + byte[] min2 = new byte[ipLength]; + byte[] max2 = new byte[ipLength]; + + for (int i = 0; i < ipLength; i++) + { + min1[i] = (byte)(ip1[i] & subnetmask1[i]); + max1[i] = (byte)(ip1[i] & subnetmask1[i] | ~subnetmask1[i]); + + min2[i] = (byte)(ip2[i] & subnetmask2[i]); + max2[i] = (byte)(ip2[i] & subnetmask2[i] | ~subnetmask2[i]); + } + + return new byte[][] { min1, max1, min2, max2 }; + } + + private void CheckPermittedEmail(ISet permitted, String email) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (EmailIsConstrained(email, str)) + { + return; + } + } + + if (email.Length == 0 && permitted.Count == 0) + { + return; + } + + throw new PkixNameConstraintValidatorException( + "Subject email address is not from a permitted subtree."); + } + + private void CheckExcludedEmail(ISet excluded, String email) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = (String)it.Current; + + if (EmailIsConstrained(email, str)) + { + throw new PkixNameConstraintValidatorException( + "Email address is from an excluded subtree."); + } + } + } + + /** + * Checks if the IP ip is included in the permitted ISet + * permitted. + * + * @param permitted A Set of permitted IP addresses with + * their subnet mask as byte arrays. + * @param ip The IP address. + * @throws PkixNameConstraintValidatorException + * if the IP is not permitted. + */ + private void CheckPermittedIP(ISet permitted, byte[] ip) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + byte[] ipWithSubnet = (byte[])it.Current; + + if (IsIPConstrained(ip, ipWithSubnet)) + { + return; + } + } + if (ip.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "IP is not from a permitted subtree."); + } + + /** + * Checks if the IP ip is included in the excluded ISet + * excluded. + * + * @param excluded A Set of excluded IP addresses with their + * subnet mask as byte arrays. + * @param ip The IP address. + * @throws PkixNameConstraintValidatorException + * if the IP is excluded. + */ + private void checkExcludedIP(ISet excluded, byte[] ip) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + byte[] ipWithSubnet = (byte[])it.Current; + + if (IsIPConstrained(ip, ipWithSubnet)) + { + throw new PkixNameConstraintValidatorException( + "IP is from an excluded subtree."); + } + } + } + + /** + * Checks if the IP address ip is constrained by + * constraint. + * + * @param ip The IP address. + * @param constraint The constraint. This is an IP address concatenated with + * its subnetmask. + * @return true if constrained, false + * otherwise. + */ + private bool IsIPConstrained(byte[] ip, byte[] constraint) + { + int ipLength = ip.Length; + + if (ipLength != (constraint.Length / 2)) + { + return false; + } + + byte[] subnetMask = new byte[ipLength]; + Array.Copy(constraint, ipLength, subnetMask, 0, ipLength); + + byte[] permittedSubnetAddress = new byte[ipLength]; + + byte[] ipSubnetAddress = new byte[ipLength]; + + // the resulting IP address by applying the subnet mask + for (int i = 0; i < ipLength; i++) + { + permittedSubnetAddress[i] = (byte)(constraint[i] & subnetMask[i]); + ipSubnetAddress[i] = (byte)(ip[i] & subnetMask[i]); + } + + return Org.BouncyCastle.Utilities.Arrays.AreEqual(permittedSubnetAddress, ipSubnetAddress); + } + + private bool EmailIsConstrained(String email, String constraint) + { + String sub = email.Substring(email.IndexOf('@') + 1); + // a particular mailbox + if (constraint.IndexOf('@') != -1) + { + if (email.ToUpper().Equals(constraint.ToUpper())) + { + return true; + } + } + // on particular host + else if (!(constraint[0].Equals('.'))) + { + if (sub.ToUpper().Equals(constraint.ToUpper())) + { + return true; + } + } + // address in sub domain + else if (WithinDomain(sub, constraint)) + { + return true; + } + return false; + } + + private bool WithinDomain(String testDomain, String domain) + { + String tempDomain = domain; + if (tempDomain.StartsWith(".")) + { + tempDomain = tempDomain.Substring(1); + } + String[] domainParts = tempDomain.Split('.'); // Strings.split(tempDomain, '.'); + String[] testDomainParts = testDomain.Split('.'); // Strings.split(testDomain, '.'); + + // must have at least one subdomain + if (testDomainParts.Length <= domainParts.Length) + { + return false; + } + + int d = testDomainParts.Length - domainParts.Length; + for (int i = -1; i < domainParts.Length; i++) + { + if (i == -1) + { + if (testDomainParts[i + d].Equals("")) + { + return false; + } + } + else if (!(String.Compare(testDomainParts[i + d], domainParts[i], true)==0)) + { + return false; + } + } + return true; + } + + private void CheckPermittedDNS(ISet permitted, String dns) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + // is sub domain + if (WithinDomain(dns, str) || dns.ToUpper().Equals(str.ToUpper())) + { + return; + } + } + if (dns.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "DNS is not from a permitted subtree."); + } + + private void checkExcludedDNS(ISet excluded, String dns) + // throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + // is sub domain or the same + if (WithinDomain(dns, str) || (String.Compare(dns,str,true) == 0)) + { + throw new PkixNameConstraintValidatorException( + "DNS is from an excluded subtree."); + } + } + } + + /** + * The common part of email1 and email2 is + * added to the union union. If email1 and + * email2 have nothing in common they are added both. + * + * @param email1 Email address constraint 1. + * @param email2 Email address constraint 2. + * @param union The union. + */ + private void unionEmail(String email1, String email2, ISet union) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (String.Compare(email1, email2, true) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(_sub, email2, true)==0) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email1 specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) + || String.Compare(email1, email2, true) == 0) + { + union.Add(email2); + } + else if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (String.Compare(_sub, email1, true) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(email1, email2, true) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + } + + private void unionURI(String email1, String email2, ISet union) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (String.Compare(email1, email2, true) ==0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(_sub, email2, true)==0) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + + } + } + } + // email1 specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) + || String.Compare(email1, email2, true) == 0) + { + union.Add(email2); + } + else if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (String.Compare(_sub, email1, true)==0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(email1, email2, true)==0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + } + + private ISet intersectDNS(ISet permitted, ISet dnss) + { + ISet intersect = new HashSet(); + for (IEnumerator it = dnss.GetEnumerator(); it.MoveNext(); ) + { + String dns = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + if (permitted == null) + { + if (dns != null) + { + intersect.Add(dns); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + + if (WithinDomain(_permitted, dns)) + { + intersect.Add(_permitted); + } + else if (WithinDomain(dns, _permitted)) + { + intersect.Add(dns); + } + } + } + } + + return intersect; + } + + protected ISet unionDNS(ISet excluded, String dns) + { + if (excluded.IsEmpty) + { + if (dns == null) + { + return excluded; + } + excluded.Add(dns); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator _iter = excluded.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + + if (WithinDomain(_permitted, dns)) + { + union.Add(dns); + } + else if (WithinDomain(dns, _permitted)) + { + union.Add(_permitted); + } + else + { + union.Add(_permitted); + union.Add(dns); + } + } + + return union; + } + } + + /** + * The most restricting part from email1 and + * email2 is added to the intersection intersect. + * + * @param email1 Email address constraint 1. + * @param email2 Email address constraint 2. + * @param intersect The intersection. + */ + private void intersectEmail(String email1, String email2, ISet intersect) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (String.Compare(email1, email2, true) == 0) + { + intersect.Add(email1); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(_sub, email2, true) == 0) + { + intersect.Add(email1); + } + } + } + // email specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) + || (String.Compare(email1, email2, true) == 0)) + { + intersect.Add(email1); + } + else if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + } + // email1 specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email2.IndexOf('@') + 1); + if (String.Compare(_sub, email1, true) == 0) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(email1, email2, true) == 0) + { + intersect.Add(email1); + } + } + } + } + + private void checkExcludedURI(ISet excluded, String uri) + // throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (IsUriConstrained(uri, str)) + { + throw new PkixNameConstraintValidatorException( + "URI is from an excluded subtree."); + } + } + } + + private ISet intersectURI(ISet permitted, ISet uris) + { + ISet intersect = new HashSet(); + for (IEnumerator it = uris.GetEnumerator(); it.MoveNext(); ) + { + String uri = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + if (permitted == null) + { + if (uri != null) + { + intersect.Add(uri); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + intersectURI(_permitted, uri, intersect); + } + } + } + return intersect; + } + + private ISet unionURI(ISet excluded, String uri) + { + if (excluded.IsEmpty) + { + if (uri == null) + { + return excluded; + } + excluded.Add(uri); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator _iter = excluded.GetEnumerator(); + while (_iter.MoveNext()) + { + String _excluded = (String)_iter.Current; + + unionURI(_excluded, uri, union); + } + + return union; + } + } + + private void intersectURI(String email1, String email2, ISet intersect) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (String.Compare(email1, email2, true) == 0) + { + intersect.Add(email1); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(_sub, email2, true) == 0) + { + intersect.Add(email1); + } + } + } + // email specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) + || (String.Compare(email1, email2, true) == 0)) + { + intersect.Add(email1); + } + else if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + } + // email1 specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email2.IndexOf('@') + 1); + if (String.Compare(_sub, email1, true) == 0) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (String.Compare(email1, email2, true) == 0) + { + intersect.Add(email1); + } + } + } + } + + private void CheckPermittedURI(ISet permitted, String uri) + // throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (IsUriConstrained(uri, str)) + { + return; + } + } + if (uri.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "URI is not from a permitted subtree."); + } + + private bool IsUriConstrained(String uri, String constraint) + { + String host = ExtractHostFromURL(uri); + // a host + if (!constraint.StartsWith(".")) + { + if (String.Compare(host, constraint, true) == 0) + { + return true; + } + } + + // in sub domain or domain + else if (WithinDomain(host, constraint)) + { + return true; + } + + return false; + } + + private static String ExtractHostFromURL(String url) + { + // see RFC 1738 + // remove ':' after protocol, e.g. http: + String sub = url.Substring(url.IndexOf(':') + 1); + // extract host from Common Internet Scheme Syntax, e.g. http:// + if (sub.IndexOf("//") != -1) + { + sub = sub.Substring(sub.IndexOf("//") + 2); + } + // first remove port, e.g. http://test.com:21 + if (sub.LastIndexOf(':') != -1) + { + sub = sub.Substring(0, sub.LastIndexOf(':')); + } + // remove user and password, e.g. http://john:password@test.com + sub = sub.Substring(sub.IndexOf(':') + 1); + sub = sub.Substring(sub.IndexOf('@') + 1); + // remove local parts, e.g. http://test.com/bla + if (sub.IndexOf('/') != -1) + { + sub = sub.Substring(0, sub.IndexOf('/')); + } + return sub; + } + + /** + * Checks if the given GeneralName is in the permitted ISet. + * + * @param name The GeneralName + * @throws PkixNameConstraintValidatorException + * If the name + */ + public void checkPermitted(GeneralName name) + // throws PkixNameConstraintValidatorException + { + switch (name.TagNo) + { + case 1: + CheckPermittedEmail(permittedSubtreesEmail, + ExtractNameAsString(name)); + break; + case 2: + CheckPermittedDNS(permittedSubtreesDNS, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 4: + CheckPermittedDN(Asn1Sequence.GetInstance(name.Name.ToAsn1Object())); + break; + case 6: + CheckPermittedURI(permittedSubtreesURI, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 7: + byte[] ip = Asn1OctetString.GetInstance(name.Name).GetOctets(); + + CheckPermittedIP(permittedSubtreesIP, ip); + break; + } + } + + /** + * Check if the given GeneralName is contained in the excluded ISet. + * + * @param name The GeneralName. + * @throws PkixNameConstraintValidatorException + * If the name is + * excluded. + */ + public void checkExcluded(GeneralName name) + // throws PkixNameConstraintValidatorException + { + switch (name.TagNo) + { + case 1: + CheckExcludedEmail(excludedSubtreesEmail, ExtractNameAsString(name)); + break; + case 2: + checkExcludedDNS(excludedSubtreesDNS, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 4: + CheckExcludedDN(Asn1Sequence.GetInstance(name.Name.ToAsn1Object())); + break; + case 6: + checkExcludedURI(excludedSubtreesURI, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 7: + byte[] ip = Asn1OctetString.GetInstance(name.Name).GetOctets(); + + checkExcludedIP(excludedSubtreesIP, ip); + break; + } + } + + /** + * Updates the permitted ISet of these name constraints with the intersection + * with the given subtree. + * + * @param permitted The permitted subtrees + */ + + public void IntersectPermittedSubtree(Asn1Sequence permitted) + { + IDictionary subtreesMap = new Hashtable(); + + // group in ISets in a map ordered by tag no. + for (IEnumerator e = permitted.GetEnumerator(); e.MoveNext(); ) + { + GeneralSubtree subtree = GeneralSubtree.GetInstance(e.Current); + + int tagNo = subtree.Base.TagNo; + if (subtreesMap[tagNo] == null) + { + subtreesMap[tagNo] = new HashSet(); + } + + ((ISet)subtreesMap[tagNo]).Add(subtree); + } + + for (IEnumerator it = subtreesMap.GetEnumerator(); it.MoveNext(); ) + { + DictionaryEntry entry = (DictionaryEntry)it.Current; + + // go through all subtree groups + switch ((int)entry.Key ) + { + case 1: + permittedSubtreesEmail = IntersectEmail(permittedSubtreesEmail, + (ISet)entry.Value); + break; + case 2: + permittedSubtreesDNS = intersectDNS(permittedSubtreesDNS, + (ISet)entry.Value); + break; + case 4: + permittedSubtreesDN = IntersectDN(permittedSubtreesDN, + (ISet)entry.Value); + break; + case 6: + permittedSubtreesURI = intersectURI(permittedSubtreesURI, + (ISet)entry.Value); + break; + case 7: + permittedSubtreesIP = IntersectIP(permittedSubtreesIP, + (ISet)entry.Value); + break; + } + } + } + + private String ExtractNameAsString(GeneralName name) + { + return DerIA5String.GetInstance(name.Name).GetString(); + } + + public void IntersectEmptyPermittedSubtree(int nameType) + { + switch (nameType) + { + case 1: + permittedSubtreesEmail = new HashSet(); + break; + case 2: + permittedSubtreesDNS = new HashSet(); + break; + case 4: + permittedSubtreesDN = new HashSet(); + break; + case 6: + permittedSubtreesURI = new HashSet(); + break; + case 7: + permittedSubtreesIP = new HashSet(); + break; + } + } + + /** + * Adds a subtree to the excluded ISet of these name constraints. + * + * @param subtree A subtree with an excluded GeneralName. + */ + public void AddExcludedSubtree(GeneralSubtree subtree) + { + GeneralName subTreeBase = subtree.Base; + + switch (subTreeBase.TagNo) + { + case 1: + excludedSubtreesEmail = UnionEmail(excludedSubtreesEmail, + ExtractNameAsString(subTreeBase)); + break; + case 2: + excludedSubtreesDNS = unionDNS(excludedSubtreesDNS, + ExtractNameAsString(subTreeBase)); + break; + case 4: + excludedSubtreesDN = UnionDN(excludedSubtreesDN, + (Asn1Sequence)subTreeBase.Name.ToAsn1Object()); + break; + case 6: + excludedSubtreesURI = unionURI(excludedSubtreesURI, + ExtractNameAsString(subTreeBase)); + break; + case 7: + excludedSubtreesIP = UnionIP(excludedSubtreesIP, Asn1OctetString + .GetInstance(subTreeBase.Name).GetOctets()); + break; + } + } + + /** + * Returns the maximum IP address. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The maximum IP address. + */ + private static byte[] Max(byte[] ip1, byte[] ip2) + { + for (int i = 0; i < ip1.Length; i++) + { + if ((ip1[i] & 0xFFFF) > (ip2[i] & 0xFFFF)) + { + return ip1; + } + } + return ip2; + } + + /** + * Returns the minimum IP address. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The minimum IP address. + */ + private static byte[] Min(byte[] ip1, byte[] ip2) + { + for (int i = 0; i < ip1.Length; i++) + { + if ((ip1[i] & 0xFFFF) < (ip2[i] & 0xFFFF)) + { + return ip1; + } + } + return ip2; + } + + /** + * Compares IP address ip1 with ip2. If ip1 + * is equal to ip2 0 is returned. If ip1 is bigger 1 is returned, -1 + * otherwise. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return 0 if ip1 is equal to ip2, 1 if ip1 is bigger, -1 otherwise. + */ + private static int CompareTo(byte[] ip1, byte[] ip2) + { + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(ip1, ip2)) + { + return 0; + } + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(Max(ip1, ip2), ip1)) + { + return 1; + } + return -1; + } + + /** + * Returns the logical OR of the IP addresses ip1 and + * ip2. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The OR of ip1 and ip2. + */ + private static byte[] Or(byte[] ip1, byte[] ip2) + { + byte[] temp = new byte[ip1.Length]; + for (int i = 0; i < ip1.Length; i++) + { + temp[i] = (byte)(ip1[i] | ip2[i]); + } + return temp; + } + + public int HashCode() + { + return HashCollection(excludedSubtreesDN) + + HashCollection(excludedSubtreesDNS) + + HashCollection(excludedSubtreesEmail) + + HashCollection(excludedSubtreesIP) + + HashCollection(excludedSubtreesURI) + + HashCollection(permittedSubtreesDN) + + HashCollection(permittedSubtreesDNS) + + HashCollection(permittedSubtreesEmail) + + HashCollection(permittedSubtreesIP) + + HashCollection(permittedSubtreesURI); + } + + private int HashCollection(ICollection coll) + { + if (coll == null) + { + return 0; + } + int hash = 0; + IEnumerator it1 = coll.GetEnumerator(); + while (it1.MoveNext()) + { + Object o = it1.Current; + if (o is byte[]) + { + hash += Org.BouncyCastle.Utilities.Arrays.GetHashCode((byte[])o); + } + else + { + hash += o.GetHashCode(); + } + } + return hash; + } + + public new bool Equals(Object o) + { + if (!(o is PkixNameConstraintValidator)) + { + return false; + } + PkixNameConstraintValidator constraintValidator = (PkixNameConstraintValidator)o; + + return CollectionsAreEqual(constraintValidator.excludedSubtreesDN, excludedSubtreesDN) + && CollectionsAreEqual(constraintValidator.excludedSubtreesDNS, excludedSubtreesDNS) + && CollectionsAreEqual(constraintValidator.excludedSubtreesEmail, excludedSubtreesEmail) + && CollectionsAreEqual(constraintValidator.excludedSubtreesIP, excludedSubtreesIP) + && CollectionsAreEqual(constraintValidator.excludedSubtreesURI, excludedSubtreesURI) + && CollectionsAreEqual(constraintValidator.permittedSubtreesDN, permittedSubtreesDN) + && CollectionsAreEqual(constraintValidator.permittedSubtreesDNS, permittedSubtreesDNS) + && CollectionsAreEqual(constraintValidator.permittedSubtreesEmail, permittedSubtreesEmail) + && CollectionsAreEqual(constraintValidator.permittedSubtreesIP, permittedSubtreesIP) + && CollectionsAreEqual(constraintValidator.permittedSubtreesURI, permittedSubtreesURI); + } + + private bool CollectionsAreEqual(ICollection coll1, ICollection coll2) + { + if (coll1 == coll2) + { + return true; + } + if (coll1 == null || coll2 == null) + { + return false; + } + if (coll1.Count != coll2.Count) + { + return false; + } + IEnumerator it1 = coll1.GetEnumerator(); + + while (it1.MoveNext()) + { + Object a = it1.Current; + IEnumerator it2 = coll2.GetEnumerator(); + bool found = false; + while (it2.MoveNext()) + { + Object b = it2.Current; + if (Equals(a, b)) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + return true; + } + + private new bool Equals(Object o1, Object o2) + { + if (o1 == o2) + { + return true; + } + if (o1 == null || o2 == null) + { + return false; + } + if ((o1 is byte[]) && (o2 is byte[])) + { + return Org.BouncyCastle.Utilities.Arrays.AreEqual((byte[])o1, (byte[])o2); + } + else + { + return o1.Equals(o2); + } + } + + /** + * Stringifies an IPv4 or v6 address with subnet mask. + * + * @param ip The IP with subnet mask. + * @return The stringified IP address. + */ + private String StringifyIP(byte[] ip) + { + String temp = ""; + for (int i = 0; i < ip.Length / 2; i++) + { + //temp += Integer.toString(ip[i] & 0x00FF) + "."; + temp += (ip[i] & 0x00FF) + "."; + } + temp = temp.Substring(0, temp.Length - 1); + temp += "/"; + for (int i = ip.Length / 2; i < ip.Length; i++) + { + //temp += Integer.toString(ip[i] & 0x00FF) + "."; + temp += (ip[i] & 0x00FF) + "."; + } + temp = temp.Substring(0, temp.Length - 1); + return temp; + } + + private String StringifyIPCollection(ISet ips) + { + String temp = ""; + temp += "["; + for (IEnumerator it = ips.GetEnumerator(); it.MoveNext(); ) + { + temp += StringifyIP((byte[])it.Current) + ","; + } + if (temp.Length > 1) + { + temp = temp.Substring(0, temp.Length - 1); + } + temp += "]"; + + return temp; + } + + public override String ToString() + { + String temp = ""; + + temp += "permitted:\n"; + if (permittedSubtreesDN != null) + { + temp += "DN:\n"; + temp += permittedSubtreesDN.ToString() + "\n"; + } + if (permittedSubtreesDNS != null) + { + temp += "DNS:\n"; + temp += permittedSubtreesDNS.ToString() + "\n"; + } + if (permittedSubtreesEmail != null) + { + temp += "Email:\n"; + temp += permittedSubtreesEmail.ToString() + "\n"; + } + if (permittedSubtreesURI != null) + { + temp += "URI:\n"; + temp += permittedSubtreesURI.ToString() + "\n"; + } + if (permittedSubtreesIP != null) + { + temp += "IP:\n"; + temp += StringifyIPCollection(permittedSubtreesIP) + "\n"; + } + temp += "excluded:\n"; + if (!(excludedSubtreesDN.IsEmpty)) + { + temp += "DN:\n"; + temp += excludedSubtreesDN.ToString() + "\n"; + } + if (!excludedSubtreesDNS.IsEmpty) + { + temp += "DNS:\n"; + temp += excludedSubtreesDNS.ToString() + "\n"; + } + if (!excludedSubtreesEmail.IsEmpty) + { + temp += "Email:\n"; + temp += excludedSubtreesEmail.ToString() + "\n"; + } + if (!excludedSubtreesURI.IsEmpty) + { + temp += "URI:\n"; + temp += excludedSubtreesURI.ToString() + "\n"; + } + if (!excludedSubtreesIP.IsEmpty) + { + temp += "IP:\n"; + temp += StringifyIPCollection(excludedSubtreesIP) + "\n"; + } + return temp; + } + + } +} diff --git a/src/core/srcbc/pkix/PkixNameConstraintValidatorException.cs b/src/core/srcbc/pkix/PkixNameConstraintValidatorException.cs new file mode 100644 index 0000000..f746b58 --- /dev/null +++ b/src/core/srcbc/pkix/PkixNameConstraintValidatorException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixNameConstraintValidatorException : Exception + { + public PkixNameConstraintValidatorException(String msg) + : base(msg) + { + } + } +} diff --git a/src/core/srcbc/pkix/PkixParameters.cs b/src/core/srcbc/pkix/PkixParameters.cs new file mode 100644 index 0000000..b433ff2 --- /dev/null +++ b/src/core/srcbc/pkix/PkixParameters.cs @@ -0,0 +1,948 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixParameters. + /// + public class PkixParameters +// : ICertPathParameters + { + // TODO Put the validity models in an enumeration? + /** + * This is the default PKIX validity model. Actually there are two variants + * of this: The PKIX model and the modified PKIX model. The PKIX model + * verifies that all involved certificates must have been valid at the + * current time. The modified PKIX model verifies that all involved + * certificates were valid at the signing time. Both are indirectly choosen + * with the {@link PKIXParameters#setDate(java.util.Date)} method, so this + * methods sets the Date when all certificates must have been + * valid. + */ + public const int PkixValidityModel = 0; + + /** + * This model uses the following validity model. Each certificate must have + * been valid at the moment where is was used. That means the end + * certificate must have been valid at the time the signature was done. The + * CA certificate which signed the end certificate must have been valid, + * when the end certificate was signed. The CA (or Root CA) certificate must + * have been valid, when the CA certificate was signed and so on. So the + * {@link PKIXParameters#setDate(java.util.Date)} method sets the time, when + * the end certificate must have been valid.

It is used e.g. + * in the German signature law. + */ + public const int ChainValidityModel = 1; + + private ISet trustAnchors; + private DateTimeObject date; + private IList certPathCheckers; + // TODO Decide whether to separate into certs/crl stores + // TODO ExtendedPkixParameters already has stores? + private ArrayList x509Stores; + private bool revocationEnabled = true; + private ISet initialPolicies; + //private bool checkOnlyEECertificateCrl = false; + private bool explicitPolicyRequired = false; + private bool anyPolicyInhibited = false; + private bool policyMappingInhibited = false; + private bool policyQualifiersRejected = true; + private IX509Selector certSelector; + private IList stores; /// TODO Redundant with other stores above? + private IX509Selector selector; + private bool additionalLocationsEnabled; + private IList additionalStores; + private ISet trustedACIssuers; + private ISet necessaryACAttributes; + private ISet prohibitedACAttributes; + private ISet attrCertCheckers; + private int validityModel = PkixValidityModel; + private bool useDeltas = false; + + /** + * Creates an instance of PKIXParameters with the specified Set of + * most-trusted CAs. Each element of the set is a TrustAnchor.
+ *
+ * Note that the Set is copied to protect against subsequent modifications. + * + * @param trustAnchors + * a Set of TrustAnchors + * + * @exception InvalidAlgorithmParameterException + * if the specified Set is empty + * (trustAnchors.isEmpty() == true) + * @exception NullPointerException + * if the specified Set is null + * @exception ClassCastException + * if any of the elements in the Set are not of type + * java.security.cert.TrustAnchor + */ + public PkixParameters( + ISet trustAnchors) + { + SetTrustAnchors(trustAnchors); + + this.initialPolicies = new HashSet(); + this.certPathCheckers = new ArrayList(); + this.x509Stores = new ArrayList(); + this.stores = new ArrayList(); + this.additionalStores = new ArrayList(); + this.trustedACIssuers = new HashSet(); + this.necessaryACAttributes = new HashSet(); + this.prohibitedACAttributes = new HashSet(); + this.attrCertCheckers = new HashSet(); + } + + // TODO implement for Pkcs12Store? +// /** +// * Creates an instance of PKIXParameters that +// * populates the set of most-trusted CAs from the trusted +// * certificate entries contained in the specified KeyStore. +// * Only keystore entries that contain trusted X509Certificates +// * are considered; all other certificate types are ignored. +// * +// * @param keystore a KeyStore from which the set of +// * most-trusted CAs will be populated +// * @throws KeyStoreException if the keystore has not been initialized +// * @throws InvalidAlgorithmParameterException if the keystore does +// * not contain at least one trusted certificate entry +// * @throws NullPointerException if the keystore is null +// */ +// public PkixParameters( +// KeyStore keystore) +//// throws KeyStoreException, InvalidAlgorithmParameterException +// { +// if (keystore == null) +// throw new ArgumentNullException("keystore"); +// ISet hashSet = new HashSet(); +// foreach (string alias in keystore.aliases()) +// { +// if (keystore.isCertificateEntry(alias)) +// { +// Certificate cert = keystore.getCertificate(alias); +// if (cert is X509Certificate) +// { +// hashSet.add(new TrustAnchor((X509Certificate)cert, null)); +// } +// } +// } +// SetTrustAnchors(hashSet); +// this.initialPolicies = new HashSet(); +// this.certPathCheckers = new ArrayList(); +// this.x509Stores = new ArrayList(); +// } + + public virtual bool IsRevocationEnabled + { + get { return revocationEnabled; } + set { revocationEnabled = value; } + } + + public virtual bool IsExplicitPolicyRequired + { + get { return explicitPolicyRequired; } + set { this.explicitPolicyRequired = value; } + } + + public virtual bool IsAnyPolicyInhibited + { + get { return anyPolicyInhibited; } + set { this.anyPolicyInhibited = value; } + } + + public virtual bool IsPolicyMappingInhibited + { + get { return policyMappingInhibited; } + set { this.policyMappingInhibited = value; } + } + + public virtual bool IsPolicyQualifiersRejected + { + get { return policyQualifiersRejected; } + set { this.policyQualifiersRejected = value; } + } + + //public bool IsCheckOnlyEECertificateCrl + //{ + // get { return this.checkOnlyEECertificateCrl; } + // set { this.checkOnlyEECertificateCrl = value; } + //} + + public virtual DateTimeObject Date + { + get { return this.date; } + set { this.date = value; } + } + + // Returns a Set of the most-trusted CAs. + public virtual ISet GetTrustAnchors() + { + return new HashSet(this.trustAnchors); + } + + // Sets the set of most-trusted CAs. + // Set is copied to protect against subsequent modifications. + public virtual void SetTrustAnchors( + ISet tas) + { + if (tas == null) + throw new ArgumentNullException("value"); + if (tas.IsEmpty) + throw new ArgumentException("non-empty set required", "value"); + + // Explicit copy to enforce type-safety + this.trustAnchors = new HashSet(); + foreach (TrustAnchor ta in tas) + { + if (ta != null) + { + trustAnchors.Add(ta); + } + } + } + + /** + * Sets the list of IX509Store's to be used in finding certificates and CRLs. + * May be null, in which case no IX509Store's will be used. The first + * IX509Store's in the list may be preferred to those that appear later.
+ *
+ * Note that the IList is copied to protect against subsequent modifications.
+ *
+ * + * @param stores + * a IList of IX509Store's (or null) + * + * @exception InvalidCastException + * if any of the elements in the list are not of type + * IX509Store + * + * @see #GetX509Stores() + */ + public virtual void SetX509Stores( + IList stores) + { + ArrayList newStores = new ArrayList(); + + if (stores != null && stores.Count != 0) + { + foreach (IX509Store obj in stores) + { + newStores.Add(obj); + } + + this.x509Stores = newStores; + } + } + + public virtual void AddX509Store( + IX509Store x509Store) + { + this.x509Stores.Add(x509Store); + } + + /** + * Returns an immutable List of IX509Stores that are used to find certificates. + * + * @return an immutable List of IX509Stores (may be empty, but never + * null) + * + * @see #setCertStores(java.util.List) + */ + public virtual IList GetX509Stores() + { + return new ArrayList(x509Stores); + } + + /** + * Returns the required constraints on the target certificate. The + * constraints are returned as an instance of CertSelector. If + * null, no constraints are defined.
+ *
+ * Note that the CertSelector returned is cloned to protect against + * subsequent modifications. + * + * @return a CertSelector specifying the constraints on the target + * certificate (or null) + * + * @see #setTargetCertConstraints(CertSelector) + */ + public virtual X509CertStoreSelector GetTargetCertConstraints() + { + if (certSelector == null) + { + return null; + } + + return (X509CertStoreSelector)certSelector.Clone(); + } + + /** + * Sets the required constraints on the target certificate. The constraints + * are specified as an instance of CertSelector. If null, no constraints are + * defined.
+ *
+ * Note that the CertSelector specified is cloned to protect against + * subsequent modifications. + * + * @param selector + * a CertSelector specifying the constraints on the target + * certificate (or null) + * + * @see #getTargetCertConstraints() + */ + public virtual void SetTargetCertConstraints( + IX509Selector selector) + { + if (selector == null) + { + certSelector = null; + } + else + { + certSelector = (IX509Selector)selector.Clone(); + } + } + + /** + * Returns an immutable Set of initial policy identifiers (OID strings), + * indicating that any one of these policies would be acceptable to the + * certificate user for the purposes of certification path processing. The + * default return value is an empty Set, which is + * interpreted as meaning that any policy would be acceptable. + * + * @return an immutable Set of initial policy OIDs in String + * format, or an empty Set (implying any policy is + * acceptable). Never returns null. + * + * @see #setInitialPolicies(java.util.Set) + */ + public virtual ISet GetInitialPolicies() + { + ISet returnSet = initialPolicies; + + // TODO Can it really be null? + if (initialPolicies == null) + { + returnSet = new HashSet(); + } + + return new HashSet(returnSet); + } + + /** + * Sets the Set of initial policy identifiers (OID strings), + * indicating that any one of these policies would be acceptable to the + * certificate user for the purposes of certification path processing. By + * default, any policy is acceptable (i.e. all policies), so a user that + * wants to allow any policy as acceptable does not need to call this + * method, or can call it with an empty Set (or + * null).
+ *
+ * Note that the Set is copied to protect against subsequent modifications.
+ *
+ * + * @param initialPolicies + * a Set of initial policy OIDs in String format (or + * null) + * + * @exception ClassCastException + * if any of the elements in the set are not of type String + * + * @see #getInitialPolicies() + */ + public virtual void SetInitialPolicies( + ISet initialPolicies) + { + this.initialPolicies = new HashSet(); + if (initialPolicies != null) + { + foreach (string obj in initialPolicies) + { + if (obj != null) + { + this.initialPolicies.Add(obj); + } + } + } + } + + /** + * Sets a List of additional certification path checkers. If + * the specified List contains an object that is not a PKIXCertPathChecker, + * it is ignored.
+ *
+ * Each PKIXCertPathChecker specified implements additional + * checks on a certificate. Typically, these are checks to process and + * verify private extensions contained in certificates. Each + * PKIXCertPathChecker should be instantiated with any + * initialization parameters needed to execute the check.
+ *
+ * This method allows sophisticated applications to extend a PKIX + * CertPathValidator or CertPathBuilder. Each + * of the specified PKIXCertPathCheckers will be called, in turn, by a PKIX + * CertPathValidator or CertPathBuilder for + * each certificate processed or validated.
+ *
+ * Regardless of whether these additional PKIXCertPathCheckers are set, a + * PKIX CertPathValidator or CertPathBuilder + * must perform all of the required PKIX checks on each certificate. The one + * exception to this rule is if the RevocationEnabled flag is set to false + * (see the {@link #setRevocationEnabled(boolean) setRevocationEnabled} + * method).
+ *
+ * Note that the List supplied here is copied and each PKIXCertPathChecker + * in the list is cloned to protect against subsequent modifications. + * + * @param checkers + * a List of PKIXCertPathCheckers. May be null, in which case no + * additional checkers will be used. + * @exception ClassCastException + * if any of the elements in the list are not of type + * java.security.cert.PKIXCertPathChecker + * @see #getCertPathCheckers() + */ + public virtual void SetCertPathCheckers(IList checkers) + { + certPathCheckers = new ArrayList(); + if (checkers != null) + { + foreach (PkixCertPathChecker obj in checkers) + { + certPathCheckers.Add(obj.Clone()); + } + } + } + + /** + * Returns the List of certification path checkers. The returned List is + * immutable, and each PKIXCertPathChecker in the List is cloned to protect + * against subsequent modifications. + * + * @return an immutable List of PKIXCertPathCheckers (may be empty, but not + * null) + * + * @see #setCertPathCheckers(java.util.List) + */ + public virtual IList GetCertPathCheckers() + { + IList checkers = new ArrayList(); + foreach (PkixCertPathChecker obj in certPathCheckers) + { + checkers.Add(obj.Clone()); + } + return ArrayList.ReadOnly(checkers); + } + + /** + * Adds a PKIXCertPathChecker to the list of certification + * path checkers. See the {@link #setCertPathCheckers setCertPathCheckers} + * method for more details. + *

+ * Note that the PKIXCertPathChecker is cloned to protect + * against subsequent modifications.

+ * + * @param checker a PKIXCertPathChecker to add to the list of + * checks. If null, the checker is ignored (not added to list). + */ + public virtual void AddCertPathChecker( + PkixCertPathChecker checker) + { + if (checker != null) + { + certPathCheckers.Add(checker.Clone()); + } + } + + public virtual object Clone() + { + // FIXME Check this whole method against the Java implementation! + + PkixParameters parameters = new PkixParameters(GetTrustAnchors()); + parameters.SetParams(this); + return parameters; + + +// PkixParameters obj = new PkixParameters(new HashSet()); +//// (PkixParameters) this.MemberwiseClone(); +// obj.x509Stores = new ArrayList(x509Stores); +// obj.certPathCheckers = new ArrayList(certPathCheckers); +// +// //Iterator iter = certPathCheckers.iterator(); +// //obj.certPathCheckers = new ArrayList(); +// //while (iter.hasNext()) +// //{ +// // obj.certPathCheckers.add(((PKIXCertPathChecker)iter.next()) +// // .clone()); +// //} +// //if (initialPolicies != null) +// //{ +// // obj.initialPolicies = new HashSet(initialPolicies); +// //} +//// if (trustAnchors != null) +//// { +//// obj.trustAnchors = new HashSet(trustAnchors); +//// } +//// if (certSelector != null) +//// { +//// obj.certSelector = (X509CertStoreSelector) certSelector.Clone(); +//// } +// return obj; + } + + /** + * Method to support Clone() under J2ME. + * super.Clone() does not exist and fields are not copied. + * + * @param params Parameters to set. If this are + * ExtendedPkixParameters they are copied to. + */ + protected virtual void SetParams( + PkixParameters parameters) + { + Date = parameters.Date; + SetCertPathCheckers(parameters.GetCertPathCheckers()); + SetX509Stores(parameters.GetX509Stores()); + IsAnyPolicyInhibited = parameters.IsAnyPolicyInhibited; + IsExplicitPolicyRequired = parameters.IsExplicitPolicyRequired; + IsPolicyMappingInhibited = parameters.IsPolicyMappingInhibited; + IsRevocationEnabled = parameters.IsRevocationEnabled; + SetInitialPolicies(parameters.GetInitialPolicies()); + IsPolicyQualifiersRejected = parameters.IsPolicyQualifiersRejected; + SetTargetCertConstraints(parameters.GetTargetCertConstraints()); + SetTrustAnchors(parameters.GetTrustAnchors()); + + validityModel = parameters.validityModel; + useDeltas = parameters.useDeltas; + additionalLocationsEnabled = parameters.additionalLocationsEnabled; + selector = parameters.selector == null ? null + : (IX509Selector) parameters.selector.Clone(); + stores = new ArrayList(parameters.stores); + additionalStores = new ArrayList(parameters.additionalStores); + trustedACIssuers = new HashSet(parameters.trustedACIssuers); + prohibitedACAttributes = new HashSet(parameters.prohibitedACAttributes); + necessaryACAttributes = new HashSet(parameters.necessaryACAttributes); + attrCertCheckers = new HashSet(parameters.attrCertCheckers); + } + + /** + * Whether delta CRLs should be used for checking the revocation status. + * Defaults to false. + */ + public virtual bool IsUseDeltasEnabled + { + get { return useDeltas; } + set { useDeltas = value; } + } + + /** + * The validity model. + * @see #CHAIN_VALIDITY_MODEL + * @see #PKIX_VALIDITY_MODEL + */ + public virtual int ValidityModel + { + get { return validityModel; } + set { validityModel = value; } + } + + /** + * Sets the Bouncy Castle Stores for finding CRLs, certificates, attribute + * certificates or cross certificates. + *

+ * The IList is cloned. + *

+ * + * @param stores A list of stores to use. + * @see #getStores + * @throws ClassCastException if an element of stores is not + * a {@link Store}. + */ + public virtual void SetStores( + IList stores) + { + if (stores == null) + { + this.stores = new ArrayList(); + } + else + { + foreach (object obj in stores) + { + if (!(obj is IX509Store)) + { + throw new InvalidCastException( + "All elements of list must be of type " + typeof(IX509Store).FullName); + } + } + this.stores = new ArrayList(stores); + } + } + + /** + * Adds a Bouncy Castle {@link Store} to find CRLs, certificates, attribute + * certificates or cross certificates. + *

+ * This method should be used to add local stores, like collection based + * X.509 stores, if available. Local stores should be considered first, + * before trying to use additional (remote) locations, because they do not + * need possible additional network traffic. + *

+ * If store is null it is ignored. + *

+ * + * @param store The store to add. + * @see #getStores + */ + public virtual void AddStore( + IX509Store store) + { + if (store != null) + { + stores.Add(store); + } + } + + /** + * Adds a additional Bouncy Castle {@link Store} to find CRLs, certificates, + * attribute certificates or cross certificates. + *

+ * You should not use this method. This method is used for adding additional + * X.509 stores, which are used to add (remote) locations, e.g. LDAP, found + * during X.509 object processing, e.g. in certificates or CRLs. This method + * is used in PKIX certification path processing. + *

+ * If store is null it is ignored. + *

+ * + * @param store The store to add. + * @see #getStores() + */ + public virtual void AddAddionalStore( + IX509Store store) + { + if (store != null) + { + additionalStores.Add(store); + } + } + + /** + * Returns an immutable IList of additional Bouncy Castle + * Stores used for finding CRLs, certificates, attribute + * certificates or cross certificates. + * + * @return an immutable IList of additional Bouncy Castle + * Stores. Never null. + * + * @see #addAddionalStore(Store) + */ + public virtual IList GetAdditionalStores() + { + return ArrayList.ReadOnly(new ArrayList(additionalStores)); + } + + /** + * Returns an immutable IList of Bouncy Castle + * Stores used for finding CRLs, certificates, attribute + * certificates or cross certificates. + * + * @return an immutable IList of Bouncy Castle + * Stores. Never null. + * + * @see #setStores(IList) + */ + public virtual IList GetStores() + { + return ArrayList.ReadOnly(new ArrayList(stores)); + } + + /** + * Returns if additional {@link X509Store}s for locations like LDAP found + * in certificates or CRLs should be used. + * + * @return Returns true if additional stores are used. + */ + public virtual bool IsAdditionalLocationsEnabled + { + get { return additionalLocationsEnabled; } + } + + /** + * Sets if additional {@link X509Store}s for locations like LDAP found in + * certificates or CRLs should be used. + * + * @param enabled true if additional stores are used. + */ + public virtual void SetAdditionalLocationsEnabled( + bool enabled) + { + additionalLocationsEnabled = enabled; + } + + /** + * Returns the required constraints on the target certificate or attribute + * certificate. The constraints are returned as an instance of + * IX509Selector. If null, no constraints are + * defined. + * + *

+ * The target certificate in a PKIX path may be a certificate or an + * attribute certificate. + *

+ * Note that the IX509Selector returned is cloned to protect + * against subsequent modifications. + *

+ * @return a IX509Selector specifying the constraints on the + * target certificate or attribute certificate (or null) + * @see #setTargetConstraints + * @see X509CertStoreSelector + * @see X509AttributeCertStoreSelector + */ + public virtual IX509Selector GetTargetConstraints() + { + if (selector != null) + { + return (IX509Selector) selector.Clone(); + } + else + { + return null; + } + } + + /** + * Sets the required constraints on the target certificate or attribute + * certificate. The constraints are specified as an instance of + * IX509Selector. If null, no constraints are + * defined. + *

+ * The target certificate in a PKIX path may be a certificate or an + * attribute certificate. + *

+ * Note that the IX509Selector specified is cloned to protect + * against subsequent modifications. + *

+ * + * @param selector a IX509Selector specifying the constraints on + * the target certificate or attribute certificate (or + * null) + * @see #getTargetConstraints + * @see X509CertStoreSelector + * @see X509AttributeCertStoreSelector + */ + public virtual void SetTargetConstraints(IX509Selector selector) + { + if (selector != null) + { + this.selector = (IX509Selector) selector.Clone(); + } + else + { + this.selector = null; + } + } + + /** + * Returns the trusted attribute certificate issuers. If attribute + * certificates is verified the trusted AC issuers must be set. + *

+ * The returned ISet consists of TrustAnchors. + *

+ * The returned ISet is immutable. Never null + *

+ * + * @return Returns an immutable set of the trusted AC issuers. + */ + public virtual ISet GetTrustedACIssuers() + { + return new HashSet(trustedACIssuers); + } + + /** + * Sets the trusted attribute certificate issuers. If attribute certificates + * is verified the trusted AC issuers must be set. + *

+ * The trustedACIssuers must be a ISet of + * TrustAnchor + *

+ * The given set is cloned. + *

+ * + * @param trustedACIssuers The trusted AC issuers to set. Is never + * null. + * @throws ClassCastException if an element of stores is not + * a TrustAnchor. + */ + public virtual void SetTrustedACIssuers( + ISet trustedACIssuers) + { + if (trustedACIssuers == null) + { + this.trustedACIssuers = new HashSet(); + } + else + { + foreach (object obj in trustedACIssuers) + { + if (!(obj is TrustAnchor)) + { + throw new InvalidCastException("All elements of set must be " + + "of type " + typeof(TrustAnchor).Name + "."); + } + } + this.trustedACIssuers = new HashSet(trustedACIssuers); + } + } + + /** + * Returns the neccessary attributes which must be contained in an attribute + * certificate. + *

+ * The returned ISet is immutable and contains + * Strings with the OIDs. + *

+ * + * @return Returns the necessary AC attributes. + */ + public virtual ISet GetNecessaryACAttributes() + { + return new HashSet(necessaryACAttributes); + } + + /** + * Sets the neccessary which must be contained in an attribute certificate. + *

+ * The ISet must contain Strings with the + * OIDs. + *

+ * The set is cloned. + *

+ * + * @param necessaryACAttributes The necessary AC attributes to set. + * @throws ClassCastException if an element of + * necessaryACAttributes is not a + * String. + */ + public virtual void SetNecessaryACAttributes( + ISet necessaryACAttributes) + { + if (necessaryACAttributes == null) + { + this.necessaryACAttributes = new HashSet(); + } + else + { + foreach (object obj in necessaryACAttributes) + { + if (!(obj is string)) + { + throw new InvalidCastException("All elements of set must be " + + "of type string."); + } + } + this.necessaryACAttributes = new HashSet(necessaryACAttributes); + } + } + + /** + * Returns the attribute certificates which are not allowed. + *

+ * The returned ISet is immutable and contains + * Strings with the OIDs. + *

+ * + * @return Returns the prohibited AC attributes. Is never null. + */ + public virtual ISet GetProhibitedACAttributes() + { + return new HashSet(prohibitedACAttributes); + } + + /** + * Sets the attribute certificates which are not allowed. + *

+ * The ISet must contain Strings with the + * OIDs. + *

+ * The set is cloned. + *

+ * + * @param prohibitedACAttributes The prohibited AC attributes to set. + * @throws ClassCastException if an element of + * prohibitedACAttributes is not a + * String. + */ + public virtual void SetProhibitedACAttributes( + ISet prohibitedACAttributes) + { + if (prohibitedACAttributes == null) + { + this.prohibitedACAttributes = new HashSet(); + } + else + { + foreach (object obj in prohibitedACAttributes) + { + if (!(obj is String)) + { + throw new InvalidCastException("All elements of set must be " + + "of type string."); + } + } + this.prohibitedACAttributes = new HashSet(prohibitedACAttributes); + } + } + + /** + * Returns the attribute certificate checker. The returned set contains + * {@link PKIXAttrCertChecker}s and is immutable. + * + * @return Returns the attribute certificate checker. Is never + * null. + */ + public virtual ISet GetAttrCertCheckers() + { + return new HashSet(attrCertCheckers); + } + + /** + * Sets the attribute certificate checkers. + *

+ * All elements in the ISet must a {@link PKIXAttrCertChecker}. + *

+ *

+ * The given set is cloned. + *

+ * + * @param attrCertCheckers The attribute certificate checkers to set. Is + * never null. + * @throws ClassCastException if an element of attrCertCheckers + * is not a PKIXAttrCertChecker. + */ + public virtual void SetAttrCertCheckers( + ISet attrCertCheckers) + { + if (attrCertCheckers == null) + { + this.attrCertCheckers = new HashSet(); + } + else + { + foreach (object obj in attrCertCheckers) + { + if (!(obj is PkixAttrCertChecker)) + { + throw new InvalidCastException("All elements of set must be " + + "of type " + typeof(PkixAttrCertChecker).FullName + "."); + } + } + this.attrCertCheckers = new HashSet(attrCertCheckers); + } + } + } +} diff --git a/src/core/srcbc/pkix/PkixPolicyNode.cs b/src/core/srcbc/pkix/PkixPolicyNode.cs new file mode 100644 index 0000000..0ea2ec2 --- /dev/null +++ b/src/core/srcbc/pkix/PkixPolicyNode.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// Summary description for PkixPolicyNode. + /// + public class PkixPolicyNode +// : IPolicyNode + { + protected IList mChildren; + protected int mDepth; + protected ISet mExpectedPolicies; + protected PkixPolicyNode mParent; + protected ISet mPolicyQualifiers; + protected string mValidPolicy; + protected bool mCritical; + + public virtual int Depth + { + get { return this.mDepth; } + } + + public virtual IEnumerable Children + { + get { return ArrayList.ReadOnly(mChildren); } + } + + public virtual bool IsCritical + { + get { return this.mCritical; } + set { this.mCritical = value; } + } + + public virtual ISet PolicyQualifiers + { + get { return new HashSet(this.mPolicyQualifiers); } + } + + public virtual string ValidPolicy + { + get { return this.mValidPolicy; } + } + + public virtual bool HasChildren + { + get { return mChildren.Count != 0; } + } + + public virtual ISet ExpectedPolicies + { + get { return new HashSet(this.mExpectedPolicies); } + set { this.mExpectedPolicies = new HashSet(value); } + } + + public virtual PkixPolicyNode Parent + { + get { return this.mParent; } + set { this.mParent = value; } + } + + /// Constructors + public PkixPolicyNode( + IList children, + int depth, + ISet expectedPolicies, + PkixPolicyNode parent, + ISet policyQualifiers, + string validPolicy, + bool critical) + { + ArrayList newChildren = new ArrayList(); + if (children != null) + { + newChildren.AddRange(children); + } + + this.mChildren = newChildren; + this.mDepth = depth; + this.mExpectedPolicies = expectedPolicies; + this.mParent = parent; + this.mPolicyQualifiers = policyQualifiers; + this.mValidPolicy = validPolicy; + this.mCritical = critical; + } + + public virtual void AddChild( + PkixPolicyNode child) + { + child.Parent = this; + mChildren.Add(child); + } + + public virtual void RemoveChild( + PkixPolicyNode child) + { + mChildren.Remove(child); + } + + public override string ToString() + { + return ToString(""); + } + + public virtual string ToString( + string indent) + { + StringBuilder buf = new StringBuilder(); + buf.Append(indent); + buf.Append(mValidPolicy); + buf.Append(" {"); + buf.Append(Platform.NewLine); + + foreach (PkixPolicyNode child in mChildren) + { + buf.Append(child.ToString(indent + " ")); + } + + buf.Append(indent); + buf.Append("}"); + buf.Append(Platform.NewLine); + return buf.ToString(); + } + + public virtual object Clone() + { + return Copy(); + } + + public virtual PkixPolicyNode Copy() + { + PkixPolicyNode node = new PkixPolicyNode( + new ArrayList(), + mDepth, + new HashSet(mExpectedPolicies), + null, + new HashSet(mPolicyQualifiers), + mValidPolicy, + mCritical); + + foreach (PkixPolicyNode child in mChildren) + { + PkixPolicyNode copy = child.Copy(); + copy.Parent = node; + node.AddChild(copy); + } + + return node; + } + } +} diff --git a/src/core/srcbc/pkix/ReasonsMask.cs b/src/core/srcbc/pkix/ReasonsMask.cs new file mode 100644 index 0000000..4c53424 --- /dev/null +++ b/src/core/srcbc/pkix/ReasonsMask.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// This class helps to handle CRL revocation reasons mask. Each CRL handles a + /// certain set of revocation reasons. + /// + internal class ReasonsMask + { + private int _reasons; + + /// + /// Constructs are reason mask with the reasons. + /// + /// The reasons. + internal ReasonsMask( + int reasons) + { + _reasons = reasons; + } + + /// + /// A reason mask with no reason. + /// + internal ReasonsMask() + : this(0) + { + } + + /// + /// A mask with all revocation reasons. + /// + internal static readonly ReasonsMask AllReasons = new ReasonsMask( + ReasonFlags.AACompromise | ReasonFlags.AffiliationChanged | ReasonFlags.CACompromise + | ReasonFlags.CertificateHold | ReasonFlags.CessationOfOperation + | ReasonFlags.KeyCompromise | ReasonFlags.PrivilegeWithdrawn | ReasonFlags.Unused + | ReasonFlags.Superseded); + + /** + * Adds all reasons from the reasons mask to this mask. + * + * @param mask The reasons mask to add. + */ + internal void AddReasons( + ReasonsMask mask) + { + _reasons = _reasons | mask.Reasons.IntValue; + } + + /// + /// Returns true if this reasons mask contains all possible + /// reasons. + /// + /// true if this reasons mask contains all possible reasons. + /// + internal bool IsAllReasons + { + get { return _reasons == AllReasons._reasons; } + } + + /// + /// Intersects this mask with the given reasons mask. + /// + /// mask The mask to intersect with. + /// The intersection of this and teh given mask. + internal ReasonsMask Intersect( + ReasonsMask mask) + { + ReasonsMask _mask = new ReasonsMask(); + _mask.AddReasons(new ReasonsMask(_reasons & mask.Reasons.IntValue)); + return _mask; + } + + /// + /// Returns true if the passed reasons mask has new reasons. + /// + /// The reasons mask which should be tested for new reasons. + /// true if the passed reasons mask has new reasons. + internal bool HasNewReasons( + ReasonsMask mask) + { + return ((_reasons | mask.Reasons.IntValue ^ _reasons) != 0); + } + + /// + /// Returns the reasons in this mask. + /// + public ReasonFlags Reasons + { + get { return new ReasonFlags(_reasons); } + } + } +} diff --git a/src/core/srcbc/pkix/Rfc3280CertPathUtilities.cs b/src/core/srcbc/pkix/Rfc3280CertPathUtilities.cs new file mode 100644 index 0000000..f154e21 --- /dev/null +++ b/src/core/srcbc/pkix/Rfc3280CertPathUtilities.cs @@ -0,0 +1,2478 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class Rfc3280CertPathUtilities + { + internal static readonly string ANY_POLICY = "2.5.29.32.0"; + + // key usage bits + internal static readonly int KEY_CERT_SIGN = 5; + internal static readonly int CRL_SIGN = 6; + + /** + * If the complete CRL includes an issuing distribution point (IDP) CRL + * extension check the following: + *

+ * (i) If the distribution point name is present in the IDP CRL extension + * and the distribution field is present in the DP, then verify that one of + * the names in the IDP matches one of the names in the DP. If the + * distribution point name is present in the IDP CRL extension and the + * distribution field is omitted from the DP, then verify that one of the + * names in the IDP matches one of the names in the cRLIssuer field of the + * DP. + *

+ *

+ * (ii) If the onlyContainsUserCerts boolean is asserted in the IDP CRL + * extension, verify that the certificate does not include the basic + * constraints extension with the cA boolean asserted. + *

+ *

+ * (iii) If the onlyContainsCACerts boolean is asserted in the IDP CRL + * extension, verify that the certificate includes the basic constraints + * extension with the cA boolean asserted. + *

+ *

+ * (iv) Verify that the onlyContainsAttributeCerts boolean is not asserted. + *

+ * + * @param dp The distribution point. + * @param cert The certificate. + * @param crl The CRL. + * @throws AnnotatedException if one of the conditions is not met or an error occurs. + */ + internal static void ProcessCrlB2( + DistributionPoint dp, + object cert, + X509Crl crl) + { + IssuingDistributionPoint idp = null; + try + { + idp = IssuingDistributionPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(crl, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception("0 Issuing distribution point extension could not be decoded.", e); + } + // (b) (2) (i) + // distribution point name is present + if (idp != null) + { + if (idp.DistributionPoint != null) + { + // make list of names + DistributionPointName dpName = IssuingDistributionPoint.GetInstance(idp).DistributionPoint; + IList names = new ArrayList(); + + if (dpName.PointType == DistributionPointName.FullName) + { + GeneralName[] genNames = GeneralNames.GetInstance(dpName.Name).GetNames(); + for (int j = 0; j < genNames.Length; j++) + { + names.Add(genNames[j]); + } + } + if (dpName.PointType == DistributionPointName.NameRelativeToCrlIssuer) + { + Asn1EncodableVector vec = new Asn1EncodableVector(); + try + { + IEnumerator e = Asn1Sequence.GetInstance( + Asn1Sequence.FromByteArray(crl.IssuerDN.GetEncoded())).GetEnumerator(); + while (e.MoveNext()) + { + vec.Add((Asn1Encodable)e.Current); + } + } + catch (IOException e) + { + throw new Exception("Could not read CRL issuer.", e); + } + vec.Add(dpName.Name); + names.Add(new GeneralName(X509Name.GetInstance(new DerSequence(vec)))); + } + bool matches = false; + // verify that one of the names in the IDP matches one + // of the names in the DP. + if (dp.DistributionPointName != null) + { + dpName = dp.DistributionPointName; + GeneralName[] genNames = null; + if (dpName.PointType == DistributionPointName.FullName) + { + genNames = GeneralNames.GetInstance(dpName.Name).GetNames(); + } + if (dpName.PointType == DistributionPointName.NameRelativeToCrlIssuer) + { + if (dp.CrlIssuer != null) + { + genNames = dp.CrlIssuer.GetNames(); + } + else + { + genNames = new GeneralName[1]; + try + { + genNames[0] = new GeneralName( + PkixCertPathValidatorUtilities.GetIssuerPrincipal(cert)); + } + catch (IOException e) + { + throw new Exception("Could not read certificate issuer.", e); + } + } + for (int j = 0; j < genNames.Length; j++) + { + IEnumerator e = Asn1Sequence.GetInstance(genNames[j].Name.ToAsn1Object()).GetEnumerator(); + Asn1EncodableVector vec = new Asn1EncodableVector(); + while (e.MoveNext()) + { + vec.Add((Asn1Encodable)e.Current); + } + vec.Add(dpName.Name); + genNames[j] = new GeneralName(X509Name.GetInstance(new DerSequence(vec))); + } + } + if (genNames != null) + { + for (int j = 0; j < genNames.Length; j++) + { + if (names.Contains(genNames[j])) + { + matches = true; + break; + } + } + } + if (!matches) + { + throw new Exception( + "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point."); + } + } + // verify that one of the names in + // the IDP matches one of the names in the cRLIssuer field of + // the DP + else + { + if (dp.CrlIssuer == null) + { + throw new Exception("Either the cRLIssuer or the distributionPoint field must " + + "be contained in DistributionPoint."); + } + GeneralName[] genNames = dp.CrlIssuer.GetNames(); + for (int j = 0; j < genNames.Length; j++) + { + if (names.Contains(genNames[j])) + { + matches = true; + break; + } + } + if (!matches) + { + throw new Exception( + "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point."); + } + } + } + BasicConstraints bc = null; + try + { + bc = BasicConstraints.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue( + (IX509Extension)cert, X509Extensions.BasicConstraints)); + } + catch (Exception e) + { + throw new Exception("Basic constraints extension could not be decoded.", e); + } + + //if (cert is X509Certificate) + { + // (b) (2) (ii) + if (idp.OnlyContainsUserCerts && ((bc != null) && bc.IsCA())) + { + throw new Exception("CA Cert CRL only contains user certificates."); + } + + // (b) (2) (iii) + if (idp.OnlyContainsCACerts && (bc == null || !bc.IsCA())) + { + throw new Exception("End CRL only contains CA certificates."); + } + } + + // (b) (2) (iv) + if (idp.OnlyContainsAttributeCerts) + { + throw new Exception("onlyContainsAttributeCerts boolean is asserted."); + } + } + } + + internal static void ProcessCertBC( + PkixCertPath certPath, + int index, + PkixNameConstraintValidator nameConstraintValidator) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + int n = certs.Count; + // i as defined in the algorithm description + int i = n - index; + // + // (b), (c) permitted and excluded subtree checking. + // + if (!(PkixCertPathValidatorUtilities.IsSelfIssued(cert) && (i < n))) + { + X509Name principal = cert.SubjectDN; + Asn1InputStream aIn = new Asn1InputStream(principal.GetEncoded()); + Asn1Sequence dns; + + try + { + dns = DerSequence.GetInstance(aIn.ReadObject()); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Exception extracting subject name when checking subtrees.", e, certPath, index); + } + + try + { + nameConstraintValidator.CheckPermittedDN(dns); + nameConstraintValidator.CheckExcludedDN(dns); + } + catch (PkixNameConstraintValidatorException e) + { + throw new PkixCertPathValidatorException( + "Subtree check for certificate subject failed.", e, certPath, index); + } + + GeneralNames altName = null; + try + { + altName = GeneralNames.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.SubjectAlternativeName)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Subject alternative name extension could not be decoded.", e, certPath, index); + } + + ArrayList emails = X509Name.GetInstance(dns).GetValues(X509Name.EmailAddress); + foreach (string email in emails) + { + GeneralName emailAsGeneralName = new GeneralName(GeneralName.Rfc822Name, email); + try + { + nameConstraintValidator.checkPermitted(emailAsGeneralName); + nameConstraintValidator.checkExcluded(emailAsGeneralName); + } + catch (PkixNameConstraintValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Subtree check for certificate subject alternative email failed.", ex, certPath, index); + } + } + if (altName != null) + { + GeneralName[] genNames = null; + try + { + genNames = altName.GetNames(); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Subject alternative name contents could not be decoded.", e, certPath, index); + } + foreach (GeneralName genName in genNames) + { + try + { + nameConstraintValidator.checkPermitted(genName); + nameConstraintValidator.checkExcluded(genName); + } + catch (PkixNameConstraintValidatorException e) + { + throw new PkixCertPathValidatorException( + "Subtree check for certificate subject alternative name failed.", e, certPath, index); + } + } + } + } + } + + internal static void PrepareNextCertA( + PkixCertPath certPath, + int index) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // + // (a) check the policy mappings + // + Asn1Sequence pm = null; + try + { + pm = Asn1Sequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyMappings)); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Policy mappings extension could not be decoded.", ex, certPath, index); + } + if (pm != null) + { + Asn1Sequence mappings = pm; + + for (int j = 0; j < mappings.Count; j++) + { + DerObjectIdentifier issuerDomainPolicy = null; + DerObjectIdentifier subjectDomainPolicy = null; + try + { + Asn1Sequence mapping = DerSequence.GetInstance(mappings[j]); + + issuerDomainPolicy = DerObjectIdentifier.GetInstance(mapping[0]); + subjectDomainPolicy = DerObjectIdentifier.GetInstance(mapping[1]); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy mappings extension contents could not be decoded.", e, certPath, index); + } + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(issuerDomainPolicy.Id)) + throw new PkixCertPathValidatorException( + "IssuerDomainPolicy is anyPolicy", null, certPath, index); + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(subjectDomainPolicy.Id)) + throw new PkixCertPathValidatorException( + "SubjectDomainPolicy is anyPolicy,", null, certPath, index); + } + } + } + + internal static PkixPolicyNode ProcessCertD( + PkixCertPath certPath, + int index, + ISet acceptablePolicies, + PkixPolicyNode validPolicyTree, + IList[] policyNodes, + int inhibitAnyPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + int n = certs.Count; + // i as defined in the algorithm description + int i = n - index; + // + // (d) policy Information checking against initial policy and + // policy mapping + // + Asn1Sequence certPolicies = null; + try + { + certPolicies = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.CertificatePolicies)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Could not read certificate policies extension from certificate.", e, certPath, index); + } + if (certPolicies != null && validPolicyTree != null) + { + // + // (d) (1) + // + ISet pols = new HashSet(); + + foreach (Asn1Encodable ae in certPolicies) + { + PolicyInformation pInfo = PolicyInformation.GetInstance(ae.ToAsn1Object()); + DerObjectIdentifier pOid = pInfo.PolicyIdentifier; + + pols.Add(pOid.Id); + + if (!Rfc3280CertPathUtilities.ANY_POLICY.Equals(pOid.Id)) + { + ISet pq = null; + try + { + pq = PkixCertPathValidatorUtilities.GetQualifierSet(pInfo.PolicyQualifiers); + } + catch (PkixCertPathValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Policy qualifier info set could not be build.", ex, certPath, index); + } + + bool match = PkixCertPathValidatorUtilities.ProcessCertD1i(i, policyNodes, pOid, pq); + + if (!match) + { + PkixCertPathValidatorUtilities.ProcessCertD1ii(i, policyNodes, pOid, pq); + } + } + } + + if (acceptablePolicies.IsEmpty || acceptablePolicies.Contains(Rfc3280CertPathUtilities.ANY_POLICY)) + { + acceptablePolicies.Clear(); + acceptablePolicies.AddAll(pols); + } + else + { + ISet t1 = new HashSet(); + + foreach (object o in acceptablePolicies) + { + if (pols.Contains(o)) + { + t1.Add(o); + } + } + acceptablePolicies.Clear(); + acceptablePolicies.AddAll(t1); + } + + // + // (d) (2) + // + if ((inhibitAnyPolicy > 0) || ((i < n) && PkixCertPathValidatorUtilities.IsSelfIssued(cert))) + { + foreach (Asn1Encodable ae in certPolicies) + { + PolicyInformation pInfo = PolicyInformation.GetInstance(ae.ToAsn1Object()); + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(pInfo.PolicyIdentifier.Id)) + { + ISet _apq = PkixCertPathValidatorUtilities.GetQualifierSet(pInfo.PolicyQualifiers); + IList _nodes = policyNodes[i - 1]; + + for (int k = 0; k < _nodes.Count; k++) + { + PkixPolicyNode _node = (PkixPolicyNode)_nodes[k]; + + IEnumerator _policySetIter = _node.ExpectedPolicies.GetEnumerator(); + while (_policySetIter.MoveNext()) + { + object _tmp = _policySetIter.Current; + + string _policy; + if (_tmp is string) + { + _policy = (string)_tmp; + } + else if (_tmp is DerObjectIdentifier) + { + _policy = ((DerObjectIdentifier)_tmp).Id; + } + else + { + continue; + } + + bool _found = false; + + foreach (PkixPolicyNode _child in _node.Children) + { + if (_policy.Equals(_child.ValidPolicy)) + { + _found = true; + } + } + + if (!_found) + { + ISet _newChildExpectedPolicies = new HashSet(); + _newChildExpectedPolicies.Add(_policy); + + PkixPolicyNode _newChild = new PkixPolicyNode(new ArrayList(), i, + _newChildExpectedPolicies, _node, _apq, _policy, false); + _node.AddChild(_newChild); + policyNodes[i].Add(_newChild); + } + } + } + break; + } + } + } + + PkixPolicyNode _validPolicyTree = validPolicyTree; + // + // (d) (3) + // + for (int j = (i - 1); j >= 0; j--) + { + IList nodes = policyNodes[j]; + + for (int k = 0; k < nodes.Count; k++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[k]; + if (!node.HasChildren) + { + _validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(_validPolicyTree, policyNodes, + node); + if (_validPolicyTree == null) + { + break; + } + } + } + } + + // + // d (4) + // + ISet criticalExtensionOids = cert.GetCriticalExtensionOids(); + + if (criticalExtensionOids != null) + { + bool critical = criticalExtensionOids.Contains(X509Extensions.CertificatePolicies.Id); + + IList nodes = policyNodes[i]; + for (int j = 0; j < nodes.Count; j++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[j]; + node.IsCritical = critical; + } + } + return _validPolicyTree; + } + return null; + } + + /** + * If the DP includes cRLIssuer, then verify that the issuer field in the + * complete CRL matches cRLIssuer in the DP and that the complete CRL + * contains an + * g distribution point extension with the indirectCRL + * boolean asserted. Otherwise, verify that the CRL issuer matches the + * certificate issuer. + * + * @param dp The distribution point. + * @param cert The certificate ot attribute certificate. + * @param crl The CRL for cert. + * @throws AnnotatedException if one of the above conditions does not apply or an error + * occurs. + */ + internal static void ProcessCrlB1( + DistributionPoint dp, + object cert, + X509Crl crl) + { + Asn1Object idp = PkixCertPathValidatorUtilities.GetExtensionValue( + crl, X509Extensions.IssuingDistributionPoint); + + bool isIndirect = false; + if (idp != null) + { + if (IssuingDistributionPoint.GetInstance(idp).IsIndirectCrl) + { + isIndirect = true; + } + } + byte[] issuerBytes = crl.IssuerDN.GetEncoded(); + + bool matchIssuer = false; + if (dp.CrlIssuer != null) + { + GeneralName[] genNames = dp.CrlIssuer.GetNames(); + for (int j = 0; j < genNames.Length; j++) + { + if (genNames[j].TagNo == GeneralName.DirectoryName) + { + try + { + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(genNames[j].Name.ToAsn1Object().GetEncoded(), issuerBytes)) + { + matchIssuer = true; + } + } + catch (IOException e) + { + throw new Exception( + "CRL issuer information from distribution point cannot be decoded.", e); + } + } + } + if (matchIssuer && !isIndirect) + { + throw new Exception("Distribution point contains cRLIssuer field but CRL is not indirect."); + } + if (!matchIssuer) + { + throw new Exception("CRL issuer of CRL does not match CRL issuer of distribution point."); + } + } + else + { + if (crl.IssuerDN.Equivalent(PkixCertPathValidatorUtilities.GetIssuerPrincipal(cert), true)) + { + matchIssuer = true; + } + } + if (!matchIssuer) + { + throw new Exception("Cannot find matching CRL issuer for certificate."); + } + } + + internal static ReasonsMask ProcessCrlD( + X509Crl crl, + DistributionPoint dp) + //throws AnnotatedException + { + IssuingDistributionPoint idp = null; + try + { + idp = IssuingDistributionPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(crl, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception("issuing distribution point extension could not be decoded.", e); + } + + // (d) (1) + if (idp != null && idp.OnlySomeReasons != null && dp.Reasons != null) + { + return new ReasonsMask(dp.Reasons.IntValue).Intersect(new ReasonsMask(idp.OnlySomeReasons + .IntValue)); + } + // (d) (4) + if ((idp == null || idp.OnlySomeReasons == null) && dp.Reasons == null) + { + return ReasonsMask.AllReasons; + } + + // (d) (2) and (d)(3) + + ReasonsMask dpReasons = null; + + if (dp.Reasons == null) + { + dpReasons = ReasonsMask.AllReasons; + } + else + { + dpReasons = new ReasonsMask(dp.Reasons.IntValue); + } + + ReasonsMask idpReasons = null; + + if (idp == null) + { + idpReasons = ReasonsMask.AllReasons; + } + else + { + idpReasons = new ReasonsMask(idp.OnlySomeReasons.IntValue); + } + + return dpReasons.Intersect(idpReasons); + } + + /** + * Obtain and validate the certification path for the complete CRL issuer. + * If a key usage extension is present in the CRL issuer's certificate, + * verify that the cRLSign bit is set. + * + * @param crl CRL which contains revocation information for the certificate + * cert. + * @param cert The attribute certificate or certificate to check if it is + * revoked. + * @param defaultCRLSignCert The issuer certificate of the certificate cert. + * @param defaultCRLSignKey The public key of the issuer certificate + * defaultCRLSignCert. + * @param paramsPKIX paramsPKIX PKIX parameters. + * @param certPathCerts The certificates on the certification path. + * @return A Set with all keys of possible CRL issuer + * certificates. + * @throws AnnotatedException if the CRL is not valid or the status cannot be checked or + * some error occurs. + */ + internal static ISet ProcessCrlF( + X509Crl crl, + object cert, + X509Certificate defaultCRLSignCert, + AsymmetricKeyParameter defaultCRLSignKey, + PkixParameters paramsPKIX, + IList certPathCerts) + { + // (f) + + // get issuer from CRL + X509CertStoreSelector selector = new X509CertStoreSelector(); + try + { + selector.Subject = crl.IssuerDN; + } + catch (IOException e) + { + throw new Exception( + "Subject criteria for certificate selector to find issuer certificate for CRL could not be set.", e); + } + + // get CRL signing certs + ArrayList coll = new ArrayList(); + + try + { + coll.AddRange(PkixCertPathValidatorUtilities.FindCertificates(selector, paramsPKIX.GetStores())); + coll.AddRange(PkixCertPathValidatorUtilities.FindCertificates(selector, paramsPKIX.GetAdditionalStores())); + coll.AddRange(PkixCertPathValidatorUtilities.FindCertificates(selector, paramsPKIX.GetX509Stores())); + } + catch (Exception e) + { + throw new Exception("Issuer certificate for CRL cannot be searched.", e); + } + + coll.Add(defaultCRLSignCert); + + IEnumerator cert_it = coll.GetEnumerator(); + + IList validCerts = new ArrayList(); + IList validKeys = new ArrayList(); + + while (cert_it.MoveNext()) + { + X509Certificate signingCert = (X509Certificate)cert_it.Current; + + /* + * CA of the certificate, for which this CRL is checked, has also + * signed CRL, so skip the path validation, because is already done + */ + if (signingCert.Equals(defaultCRLSignCert)) + { + validCerts.Add(signingCert); + validKeys.Add(defaultCRLSignKey); + continue; + } + try + { +// CertPathBuilder builder = CertPathBuilder.GetInstance("PKIX"); + PkixCertPathBuilder builder = new PkixCertPathBuilder(); + selector = new X509CertStoreSelector(); + selector.Certificate = signingCert; + + PkixParameters temp = (PkixParameters)paramsPKIX.Clone(); + temp.SetTargetCertConstraints(selector); + + PkixBuilderParameters parameters = (PkixBuilderParameters) + PkixBuilderParameters.GetInstance(temp); + + /* + * if signingCert is placed not higher on the cert path a + * dependency loop results. CRL for cert is checked, but + * signingCert is needed for checking the CRL which is dependent + * on checking cert because it is higher in the cert path and so + * signing signingCert transitively. so, revocation is disabled, + * forgery attacks of the CRL are detected in this outer loop + * for all other it must be enabled to prevent forgery attacks + */ + if (certPathCerts.Contains(signingCert)) + { + parameters.IsRevocationEnabled = false; + } + else + { + parameters.IsRevocationEnabled = true; + } + IList certs = builder.Build(parameters).CertPath.Certificates; + validCerts.Add(signingCert); + validKeys.Add(PkixCertPathValidatorUtilities.GetNextWorkingKey(certs, 0)); + } + catch (PkixCertPathBuilderException e) + { + throw new Exception("Internal error.", e); + } + catch (PkixCertPathValidatorException e) + { + throw new Exception("Public key of issuer certificate of CRL could not be retrieved.", e); + } + //catch (Exception e) + //{ + // throw new Exception(e.Message); + //} + } + + ISet checkKeys = new HashSet(); + + Exception lastException = null; + for (int i = 0; i < validCerts.Count; i++) + { + X509Certificate signCert = (X509Certificate)validCerts[i]; + bool[] keyusage = signCert.GetKeyUsage(); + + if (keyusage != null && (keyusage.Length < 7 || !keyusage[CRL_SIGN])) + { + lastException = new Exception( + "Issuer certificate key usage extension does not permit CRL signing."); + } + else + { + checkKeys.Add(validKeys[i]); + } + } + + if ((checkKeys.Count == 0) && lastException == null) + { + throw new Exception("Cannot find a valid issuer certificate."); + } + if ((checkKeys.Count == 0) && lastException != null) + { + throw lastException; + } + + return checkKeys; + } + + internal static AsymmetricKeyParameter ProcessCrlG( + X509Crl crl, + ISet keys) + { + Exception lastException = null; + foreach (AsymmetricKeyParameter key in keys) + { + try + { + crl.Verify(key); + return key; + } + catch (Exception e) + { + lastException = e; + } + } + throw new Exception("Cannot verify CRL.", lastException); + } + + internal static X509Crl ProcessCrlH( + ISet deltaCrls, + AsymmetricKeyParameter key) + { + Exception lastException = null; + foreach (X509Crl crl in deltaCrls) + { + try + { + crl.Verify(key); + return crl; + } + catch (Exception e) + { + lastException = e; + } + } + if (lastException != null) + { + throw new Exception("Cannot verify delta CRL.", lastException); + } + return null; + } + + /** + * Checks a distribution point for revocation information for the + * certificate cert. + * + * @param dp The distribution point to consider. + * @param paramsPKIX PKIX parameters. + * @param cert Certificate to check if it is revoked. + * @param validDate The date when the certificate revocation status should be + * checked. + * @param defaultCRLSignCert The issuer certificate of the certificate cert. + * @param defaultCRLSignKey The public key of the issuer certificate + * defaultCRLSignCert. + * @param certStatus The current certificate revocation status. + * @param reasonMask The reasons mask which is already checked. + * @param certPathCerts The certificates of the certification path. + * @throws AnnotatedException if the certificate is revoked or the status cannot be checked + * or some error occurs. + */ + private static void CheckCrl( + DistributionPoint dp, + PkixParameters paramsPKIX, + X509Certificate cert, + DateTime validDate, + X509Certificate defaultCRLSignCert, + AsymmetricKeyParameter defaultCRLSignKey, + CertStatus certStatus, + ReasonsMask reasonMask, + IList certPathCerts) + //throws AnnotatedException + { + DateTime currentDate = DateTime.UtcNow; + + if (validDate.Ticks > currentDate.Ticks) + { + throw new Exception("Validation time is in future."); + } + + // (a) + /* + * We always get timely valid CRLs, so there is no step (a) (1). + * "locally cached" CRLs are assumed to be in getStore(), additional + * CRLs must be enabled in the ExtendedPKIXParameters and are in + * getAdditionalStore() + */ + + ISet crls = PkixCertPathValidatorUtilities.GetCompleteCrls(dp, cert, currentDate, paramsPKIX); + bool validCrlFound = false; + Exception lastException = null; + + IEnumerator crl_iter = crls.GetEnumerator(); + + while (crl_iter.MoveNext() && certStatus.Status == CertStatus.Unrevoked && !reasonMask.IsAllReasons) + { + try + { + X509Crl crl = (X509Crl)crl_iter.Current; + + // (d) + ReasonsMask interimReasonsMask = Rfc3280CertPathUtilities.ProcessCrlD(crl, dp); + + // (e) + /* + * The reasons mask is updated at the end, so only valid CRLs + * can update it. If this CRL does not contain new reasons it + * must be ignored. + */ + if (!interimReasonsMask.HasNewReasons(reasonMask)) + { + continue; + } + + // (f) + ISet keys = Rfc3280CertPathUtilities.ProcessCrlF(crl, cert, defaultCRLSignCert, defaultCRLSignKey, + paramsPKIX, certPathCerts); + // (g) + AsymmetricKeyParameter key = Rfc3280CertPathUtilities.ProcessCrlG(crl, keys); + + X509Crl deltaCRL = null; + + if (paramsPKIX.IsUseDeltasEnabled) + { + // get delta CRLs + ISet deltaCRLs = PkixCertPathValidatorUtilities.GetDeltaCrls(currentDate, paramsPKIX, crl); + // we only want one valid delta CRL + // (h) + deltaCRL = Rfc3280CertPathUtilities.ProcessCrlH(deltaCRLs, key); + } + + /* + * CRL must be be valid at the current time, not the validation + * time. If a certificate is revoked with reason keyCompromise, + * cACompromise, it can be used for forgery, also for the past. + * This reason may not be contained in older CRLs. + */ + + /* + * in the chain model signatures stay valid also after the + * certificate has been expired, so they do not have to be in + * the CRL validity time + */ + + if (paramsPKIX.ValidityModel != PkixParameters.ChainValidityModel) + { + /* + * if a certificate has expired, but was revoked, it is not + * more in the CRL, so it would be regarded as valid if the + * first check is not done + */ + if (cert.NotAfter.Ticks < crl.ThisUpdate.Ticks) + { + throw new Exception("No valid CRL for current time found."); + } + } + + Rfc3280CertPathUtilities.ProcessCrlB1(dp, cert, crl); + + // (b) (2) + Rfc3280CertPathUtilities.ProcessCrlB2(dp, cert, crl); + + // (c) + Rfc3280CertPathUtilities.ProcessCrlC(deltaCRL, crl, paramsPKIX); + + // (i) + Rfc3280CertPathUtilities.ProcessCrlI(validDate, deltaCRL, cert, certStatus, paramsPKIX); + + // (j) + Rfc3280CertPathUtilities.ProcessCrlJ(validDate, crl, cert, certStatus); + + // (k) + if (certStatus.Status == CrlReason.RemoveFromCrl) + { + certStatus.Status = CertStatus.Unrevoked; + } + + // update reasons mask + reasonMask.AddReasons(interimReasonsMask); + + ISet criticalExtensions = crl.GetCriticalExtensionOids(); + + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + + if (!criticalExtensions.IsEmpty) + throw new Exception("CRL contains unsupported critical extensions."); + } + + if (deltaCRL != null) + { + criticalExtensions = deltaCRL.GetCriticalExtensionOids(); + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + + if (!criticalExtensions.IsEmpty) + throw new Exception("Delta CRL contains unsupported critical extension."); + } + } + + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + if (!validCrlFound) + { + throw lastException; + } + } + + /** + * Checks a certificate if it is revoked. + * + * @param paramsPKIX PKIX parameters. + * @param cert Certificate to check if it is revoked. + * @param validDate The date when the certificate revocation status should be + * checked. + * @param sign The issuer certificate of the certificate cert. + * @param workingPublicKey The public key of the issuer certificate sign. + * @param certPathCerts The certificates of the certification path. + * @throws AnnotatedException if the certificate is revoked or the status cannot be checked + * or some error occurs. + */ + protected static void CheckCrls( + PkixParameters paramsPKIX, + X509Certificate cert, + DateTime validDate, + X509Certificate sign, + AsymmetricKeyParameter workingPublicKey, + IList certPathCerts) + { + Exception lastException = null; + CrlDistPoint crldp = null; + + try + { + crldp = CrlDistPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.CrlDistributionPoints)); + } + catch (Exception e) + { + throw new Exception("CRL distribution point extension could not be read.", e); + } + + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromCrlDistributionPoint(crldp, paramsPKIX); + } + catch (Exception e) + { + throw new Exception( + "No additional CRL locations could be decoded from CRL distribution point extension.", e); + } + CertStatus certStatus = new CertStatus(); + ReasonsMask reasonsMask = new ReasonsMask(); + + bool validCrlFound = false; + + // for each distribution point + if (crldp != null) + { + DistributionPoint[] dps = null; + try + { + dps = crldp.GetDistributionPoints(); + } + catch (Exception e) + { + throw new Exception("Distribution points could not be read.", e); + } + if (dps != null) + { + for (int i = 0; i < dps.Length && certStatus.Status == CertStatus.Unrevoked && !reasonsMask.IsAllReasons; i++) + { + PkixParameters paramsPKIXClone = (PkixParameters)paramsPKIX.Clone(); + try + { + CheckCrl(dps[i], paramsPKIXClone, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask, certPathCerts); + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + } + } + + /* + * If the revocation status has not been determined, repeat the process + * above with any available CRLs not specified in a distribution point + * but issued by the certificate issuer. + */ + + if (certStatus.Status == CertStatus.Unrevoked && !reasonsMask.IsAllReasons) + { + try + { + /* + * assume a DP with both the reasons and the cRLIssuer fields + * omitted and a distribution point name of the certificate + * issuer. + */ + Asn1Object issuer = null; + try + { + issuer = new Asn1InputStream(cert.IssuerDN.GetEncoded()).ReadObject(); + } + catch (Exception e) + { + throw new Exception("Issuer from certificate for CRL could not be reencoded.", e); + } + DistributionPoint dp = new DistributionPoint(new DistributionPointName(0, new GeneralNames( + new GeneralName(GeneralName.DirectoryName, issuer))), null, null); + PkixParameters paramsPKIXClone = (PkixParameters)paramsPKIX.Clone(); + + CheckCrl(dp, paramsPKIXClone, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask, + certPathCerts); + + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + + if (!validCrlFound) + { + throw lastException; + } + if (certStatus.Status != CertStatus.Unrevoked) + { + // TODO This format is forced by the NistCertPath tests + string formattedDate = certStatus.RevocationDate.Value.ToString( + "G", CultureInfo.CreateSpecificCulture("en-us")); + string message = "Certificate revocation after " + formattedDate; + message += ", reason: " + CrlReasons[certStatus.Status]; + throw new Exception(message); + } + + if (!reasonsMask.IsAllReasons && certStatus.Status == CertStatus.Unrevoked) + { + certStatus.Status = CertStatus.Undetermined; + } + + if (certStatus.Status == CertStatus.Undetermined) + { + throw new Exception("Certificate status could not be determined."); + } + } + + internal static PkixPolicyNode PrepareCertB( + PkixCertPath certPath, + int index, + IList[] policyNodes, + PkixPolicyNode validPolicyTree, + int policyMapping) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + int n = certs.Count; + // i as defined in the algorithm description + int i = n - index; + // (b) + // + Asn1Sequence pm = null; + try + { + pm = (Asn1Sequence)Asn1Sequence.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyMappings)); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Policy mappings extension could not be decoded.", ex, certPath, index); + } + PkixPolicyNode _validPolicyTree = validPolicyTree; + if (pm != null) + { + Asn1Sequence mappings = (Asn1Sequence)pm; + IDictionary m_idp = new Hashtable(); + ISet s_idp = new HashSet(); + + for (int j = 0; j < mappings.Count; j++) + { + Asn1Sequence mapping = (Asn1Sequence) mappings[j]; + string id_p = ((DerObjectIdentifier) mapping[0]).Id; + string sd_p = ((DerObjectIdentifier) mapping[1]).Id; + ISet tmp; + + if (!m_idp.Contains(id_p)) + { + tmp = new HashSet(); + tmp.Add(sd_p); + m_idp[id_p] = tmp; + s_idp.Add(id_p); + } + else + { + tmp = (ISet)m_idp[id_p]; + tmp.Add(sd_p); + } + } + + IEnumerator it_idp = s_idp.GetEnumerator(); + while (it_idp.MoveNext()) + { + string id_p = (string)it_idp.Current; + + // + // (1) + // + if (policyMapping > 0) + { + bool idp_found = false; + IEnumerator nodes_i = policyNodes[i].GetEnumerator(); + + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (node.ValidPolicy.Equals(id_p)) + { + idp_found = true; + node.ExpectedPolicies = (ISet)m_idp[id_p]; + break; + } + } + + if (!idp_found) + { + nodes_i = policyNodes[i].GetEnumerator(); + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(node.ValidPolicy)) + { + ISet pq = null; + Asn1Sequence policies = null; + try + { + policies = (Asn1Sequence)PkixCertPathValidatorUtilities.GetExtensionValue(cert, + X509Extensions.CertificatePolicies); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Certificate policies extension could not be decoded.", e, certPath, index); + } + + foreach (Asn1Encodable ae in policies) + { + PolicyInformation pinfo = null; + try + { + pinfo = PolicyInformation.GetInstance(ae.ToAsn1Object()); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Policy information could not be decoded.", ex, certPath, index); + } + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(pinfo.PolicyIdentifier.Id)) + { + try + { + pq = PkixCertPathValidatorUtilities + .GetQualifierSet(pinfo.PolicyQualifiers); + } + catch (PkixCertPathValidatorException ex) + { + throw new PkixCertPathValidatorException( + "Policy qualifier info set could not be decoded.", ex, certPath, + index); + } + break; + } + } + bool ci = false; + ISet critExtOids = cert.GetCriticalExtensionOids(); + if (critExtOids != null) + { + ci = critExtOids.Contains(X509Extensions.CertificatePolicies.Id); + } + + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(p_node.ValidPolicy)) + { + PkixPolicyNode c_node = new PkixPolicyNode(new ArrayList(), i, + (ISet)m_idp[id_p], p_node, pq, id_p, ci); + p_node.AddChild(c_node); + policyNodes[i].Add(c_node); + } + break; + } + } + } + + // + // (2) + // + } + else if (policyMapping <= 0) + { + //IEnumerator nodes_i = policyNodes[i].GetEnumerator(); + //IEnumerator nodes_i = ArrayList.ReadOnly(policyNodes[i]).GetEnumerator(); + + IEnumerator nodes_i = new ArrayList(policyNodes[i]).GetEnumerator(); + + while (nodes_i.MoveNext()) + { + PkixPolicyNode node = (PkixPolicyNode)nodes_i.Current; + if (node.ValidPolicy.Equals(id_p)) + { + PkixPolicyNode p_node = (PkixPolicyNode)node.Parent; + //p_node.RemoveChild(node); + p_node.RemoveChild(node); + //((IList)policyNodes[i]).Remove(nodes_i.Current); + + for (int k = (i - 1); k >= 0; k--) + { + IList nodes = policyNodes[k]; + for (int l = 0; l < nodes.Count; l++) + { + PkixPolicyNode node2 = (PkixPolicyNode)nodes[l]; + if (!node2.HasChildren) + { + _validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode( + _validPolicyTree, policyNodes, node2); + if (_validPolicyTree == null) + { + break; + } + } + } + } + } + } + } + } + } + return _validPolicyTree; + } + + internal static ISet[] ProcessCrlA1ii( + DateTime currentDate, + PkixParameters paramsPKIX, + X509Certificate cert, + X509Crl crl) + { + ISet completeSet = new HashSet(); + ISet deltaSet = new HashSet(); + X509CrlStoreSelector crlselect = new X509CrlStoreSelector(); + crlselect.CertificateChecking = cert; + + if (paramsPKIX.Date != null) + { + crlselect.DateAndTime = paramsPKIX.Date; + } + else + { + crlselect.DateAndTime = new DateTimeObject(currentDate); + } + + try + { + IList issuer = new ArrayList(); + issuer.Add(crl.IssuerDN); + crlselect.Issuers = issuer; + } + catch (IOException e) + { + throw new Exception("Cannot extract issuer from CRL." + e, e); + } + + crlselect.CompleteCrlEnabled = true; + + // get complete CRL(s) + try + { + completeSet.AddAll(PkixCertPathValidatorUtilities.FindCrls(crlselect, paramsPKIX.GetAdditionalStores())); + completeSet.AddAll(PkixCertPathValidatorUtilities.FindCrls(crlselect, paramsPKIX.GetStores())); + completeSet.AddAll(PkixCertPathValidatorUtilities.FindCrls(crlselect, paramsPKIX.GetX509Stores())); + } + catch (Exception e) + { + throw new Exception("Exception obtaining complete CRLs.", e); + } + if (paramsPKIX.IsUseDeltasEnabled) + { + // get delta CRL(s) + try + { + deltaSet.AddAll(PkixCertPathValidatorUtilities.GetDeltaCrls(currentDate, paramsPKIX, crl)); + } + catch (Exception e) + { + throw new Exception("Exception obtaining delta CRLs.", e); + } + } + return new ISet[] + { + completeSet, + deltaSet}; + } + + internal static ISet ProcessCrlA1i( + DateTime currentDate, + PkixParameters paramsPKIX, + X509Certificate cert, + X509Crl crl) + { + ISet deltaSet = new HashSet(); + if (paramsPKIX.IsUseDeltasEnabled) + { + CrlDistPoint freshestCRL = null; + try + { + freshestCRL = CrlDistPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.FreshestCrl)); + } + catch (Exception e) + { + throw new Exception("Freshest CRL extension could not be decoded from certificate.", e); + } + + if (freshestCRL == null) + { + try + { + freshestCRL = CrlDistPoint.GetInstance(PkixCertPathValidatorUtilities.GetExtensionValue(crl, X509Extensions.FreshestCrl)); + } + catch (Exception e) + { + throw new Exception("Freshest CRL extension could not be decoded from CRL.", e); + } + } + if (freshestCRL != null) + { + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromCrlDistributionPoint(freshestCRL, paramsPKIX); + } + catch (Exception e) + { + throw new Exception( + "No new delta CRL locations could be added from Freshest CRL extension.", e); + } + // get delta CRL(s) + try + { + deltaSet.AddAll(PkixCertPathValidatorUtilities.GetDeltaCrls(currentDate, paramsPKIX, crl)); + } + catch (Exception e) + { + throw new Exception("Exception obtaining delta CRLs.", e); + } + } + } + return deltaSet; + } + + internal static void ProcessCertF( + PkixCertPath certPath, + int index, + PkixPolicyNode validPolicyTree, + int explicitPolicy) + { + // + // (f) + // + if (explicitPolicy <= 0 && validPolicyTree == null) + { + throw new PkixCertPathValidatorException( + "No valid policy tree found when one expected.", null, certPath, index); + } + } + + internal static void ProcessCertA( + PkixCertPath certPath, + PkixParameters paramsPKIX, + int index, + AsymmetricKeyParameter workingPublicKey, + X509Name workingIssuerName, + X509Certificate sign) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (a) verify + // + try + { + // (a) (1) + // + cert.Verify(workingPublicKey); + } + catch (GeneralSecurityException e) + { + throw new PkixCertPathValidatorException("Could not validate certificate signature.", e, certPath, index); + } + + try + { + // (a) (2) + // + cert.CheckValidity(PkixCertPathValidatorUtilities + .GetValidCertDateFromValidityModel(paramsPKIX, certPath, index)); + } + catch (CertificateExpiredException e) + { + throw new PkixCertPathValidatorException("Could not validate certificate: " + e.Message, e, certPath, index); + } + catch (CertificateNotYetValidException e) + { + throw new PkixCertPathValidatorException("Could not validate certificate: " + e.Message, e, certPath, index); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Could not validate time of certificate.", e, certPath, index); + } + + // + // (a) (3) + // + if (paramsPKIX.IsRevocationEnabled) + { + try + { + CheckCrls(paramsPKIX, cert, PkixCertPathValidatorUtilities.GetValidCertDateFromValidityModel(paramsPKIX, + certPath, index), sign, workingPublicKey, certs); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException(e.Message, e.InnerException, certPath, index); + } + } + + // + // (a) (4) name chaining + // + X509Name issuer = PkixCertPathValidatorUtilities.GetIssuerPrincipal(cert); + if (!issuer.Equivalent(workingIssuerName, true)) + { + throw new PkixCertPathValidatorException("IssuerName(" + issuer + + ") does not match SubjectName(" + workingIssuerName + ") of signing certificate.", null, + certPath, index); + } + } + + internal static int PrepareNextCertI1( + PkixCertPath certPath, + int index, + int explicitPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (i) + // + Asn1Sequence pc = null; + try + { + pc = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension cannot be decoded.", e, certPath, index); + } + + int tmpInt; + + if (pc != null) + { + IEnumerator policyConstraints = pc.GetEnumerator(); + + while (policyConstraints.MoveNext()) + { + try + { + + Asn1TaggedObject constraint = Asn1TaggedObject.GetInstance(policyConstraints.Current); + if (constraint.TagNo == 0) + { + tmpInt = DerInteger.GetInstance(constraint).Value.IntValue; + if (tmpInt < explicitPolicy) + { + return tmpInt; + } + break; + } + } + catch (ArgumentException e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension contents cannot be decoded.", e, certPath, index); + } + } + } + return explicitPolicy; + } + + internal static int PrepareNextCertI2( + PkixCertPath certPath, + int index, + int policyMapping) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (i) + // + Asn1Sequence pc = null; + try + { + pc = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension cannot be decoded.", e, certPath, index); + } + + int tmpInt; + + if (pc != null) + { + IEnumerator policyConstraints = pc.GetEnumerator(); + + while (policyConstraints.MoveNext()) + { + try + { + Asn1TaggedObject constraint = Asn1TaggedObject.GetInstance(policyConstraints.Current); + if (constraint.TagNo == 1) + { + tmpInt = DerInteger.GetInstance(constraint).Value.IntValue; + if (tmpInt < policyMapping) + { + return tmpInt; + } + break; + } + } + catch (ArgumentException e) + { + throw new PkixCertPathValidatorException( + "Policy constraints extension contents cannot be decoded.", e, certPath, index); + } + } + } + return policyMapping; + } + + internal static void PrepareNextCertG( + PkixCertPath certPath, + int index, + PkixNameConstraintValidator nameConstraintValidator) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (g) handle the name constraints extension + // + NameConstraints nc = null; + try + { + Asn1Sequence ncSeq = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.NameConstraints)); + if (ncSeq != null) + { + nc = new NameConstraints(ncSeq); + } + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Name constraints extension could not be decoded.", e, certPath, index); + } + if (nc != null) + { + // + // (g) (1) permitted subtrees + // + Asn1Sequence permitted = nc.PermittedSubtrees; + if (permitted != null) + { + try + { + nameConstraintValidator.IntersectPermittedSubtree(permitted); + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Permitted subtrees cannot be build from name constraints extension.", ex, certPath, index); + } + } + + // + // (g) (2) excluded subtrees + // + Asn1Sequence excluded = nc.ExcludedSubtrees; + if (excluded != null) + { + IEnumerator e = excluded.GetEnumerator(); + try + { + while (e.MoveNext()) + { + GeneralSubtree subtree = GeneralSubtree.GetInstance(e.Current); + nameConstraintValidator.AddExcludedSubtree(subtree); + } + } + catch (Exception ex) + { + throw new PkixCertPathValidatorException( + "Excluded subtrees cannot be build from name constraints extension.", ex, certPath, index); + } + } + } + } + + internal static int PrepareNextCertJ( + PkixCertPath certPath, + int index, + int inhibitAnyPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (j) + // + DerInteger iap = null; + try + { + iap = DerInteger.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.InhibitAnyPolicy)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Inhibit any-policy extension cannot be decoded.", e, certPath, index); + } + + if (iap != null) + { + int _inhibitAnyPolicy = iap.Value.IntValue; + + if (_inhibitAnyPolicy < inhibitAnyPolicy) + return _inhibitAnyPolicy; + } + return inhibitAnyPolicy; + } + + internal static void PrepareNextCertK( + PkixCertPath certPath, + int index) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (k) + // + BasicConstraints bc = null; + try + { + bc = BasicConstraints.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.BasicConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Basic constraints extension cannot be decoded.", e, certPath, + index); + } + if (bc != null) + { + if (!(bc.IsCA())) + throw new PkixCertPathValidatorException("Not a CA certificate"); + } + else + { + throw new PkixCertPathValidatorException("Intermediate certificate lacks BasicConstraints"); + } + } + + internal static int PrepareNextCertL( + PkixCertPath certPath, + int index, + int maxPathLength) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + // + // (l) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + if (maxPathLength <= 0) + { + throw new PkixCertPathValidatorException("Max path length not greater than zero", null, certPath, index); + } + + return maxPathLength - 1; + } + return maxPathLength; + } + + internal static int PrepareNextCertM( + PkixCertPath certPath, + int index, + int maxPathLength) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (m) + // + BasicConstraints bc = null; + try + { + bc = BasicConstraints.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.BasicConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Basic constraints extension cannot be decoded.", e, certPath, + index); + } + if (bc != null) + { + BigInteger _pathLengthConstraint = bc.PathLenConstraint; + + if (_pathLengthConstraint != null) + { + int _plc = _pathLengthConstraint.IntValue; + + if (_plc < maxPathLength) + { + return _plc; + } + } + } + return maxPathLength; + } + + internal static void PrepareNextCertN( + PkixCertPath certPath, + int index) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (n) + // + bool[] _usage = cert.GetKeyUsage(); + + if ((_usage != null) && !_usage[Rfc3280CertPathUtilities.KEY_CERT_SIGN]) + { + throw new PkixCertPathValidatorException( + "Issuer certificate keyusage extension is critical and does not permit key signing.", null, + certPath, index); + } + } + + internal static void PrepareNextCertO( + PkixCertPath certPath, + int index, + ISet criticalExtensions, + IList pathCheckers) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (o) + // + IEnumerator tmpIter = pathCheckers.GetEnumerator(); + while (tmpIter.MoveNext()) + { + try + { + ((PkixCertPathChecker)tmpIter.Current).Check(cert, criticalExtensions); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException(e.Message, e.InnerException, certPath, index); + } + } + if (!criticalExtensions.IsEmpty) + { + throw new PkixCertPathValidatorException("Certificate has unsupported critical extension.", null, certPath, + index); + } + } + + internal static int PrepareNextCertH1( + PkixCertPath certPath, + int index, + int explicitPolicy) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (h) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + // + // (1) + // + if (explicitPolicy != 0) + return explicitPolicy - 1; + } + return explicitPolicy; + } + + internal static int PrepareNextCertH2( + PkixCertPath certPath, + int index, + int policyMapping) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (h) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + // + // (2) + // + if (policyMapping != 0) + return policyMapping - 1; + } + return policyMapping; + } + + + internal static int PrepareNextCertH3( + PkixCertPath certPath, + int index, + int inhibitAnyPolicy) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (h) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert)) + { + // + // (3) + // + if (inhibitAnyPolicy != 0) + return inhibitAnyPolicy - 1; + } + return inhibitAnyPolicy; + } + + internal static int WrapupCertA( + int explicitPolicy, + X509Certificate cert) + { + // + // (a) + // + if (!PkixCertPathValidatorUtilities.IsSelfIssued(cert) && (explicitPolicy != 0)) + { + explicitPolicy--; + } + return explicitPolicy; + } + + internal static int WrapupCertB( + PkixCertPath certPath, + int index, + int explicitPolicy) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (b) + // + int tmpInt; + Asn1Sequence pc = null; + try + { + pc = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.PolicyConstraints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Policy constraints could no be decoded.", e, certPath, index); + } + + if (pc != null) + { + IEnumerator policyConstraints = pc.GetEnumerator(); + + while (policyConstraints.MoveNext()) + { + Asn1TaggedObject constraint = (Asn1TaggedObject)policyConstraints.Current; + switch (constraint.TagNo) + { + case 0: + try + { + tmpInt = DerInteger.GetInstance(constraint).Value.IntValue; + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Policy constraints requireExplicitPolicy field could no be decoded.", e, certPath, + index); + } + if (tmpInt == 0) + { + return 0; + } + break; + } + } + } + return explicitPolicy; + } + + internal static void WrapupCertF( + PkixCertPath certPath, + int index, + IList pathCheckers, + ISet criticalExtensions) + //throws CertPathValidatorException + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + IEnumerator tmpIter = pathCheckers.GetEnumerator(); + + while (tmpIter.MoveNext()) + { + try + { + ((PkixCertPathChecker)tmpIter.Current).Check(cert, criticalExtensions); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException("Additional certificate path checker failed.", e, certPath, + index); + } + } + + if (!criticalExtensions.IsEmpty) + { + throw new PkixCertPathValidatorException("Certificate has unsupported critical extension", + null, certPath, index); + } + } + + internal static PkixPolicyNode WrapupCertG( + PkixCertPath certPath, + PkixParameters paramsPKIX, + ISet userInitialPolicySet, + int index, + IList[] policyNodes, + PkixPolicyNode validPolicyTree, + ISet acceptablePolicies) + { + int n = certPath.Certificates.Count; + + // + // (g) + // + PkixPolicyNode intersection; + + // + // (g) (i) + // + if (validPolicyTree == null) + { + if (paramsPKIX.IsExplicitPolicyRequired) + { + throw new PkixCertPathValidatorException( + "Explicit policy requested but none available.", null, certPath, index); + } + intersection = null; + } + else if (PkixCertPathValidatorUtilities.IsAnyPolicy(userInitialPolicySet)) // (g) + // (ii) + { + if (paramsPKIX.IsExplicitPolicyRequired) + { + if (acceptablePolicies.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Explicit policy requested but none available.", null, certPath, index); + } + else + { + ISet _validPolicyNodeSet = new HashSet(); + + for (int j = 0; j < policyNodes.Length; j++) + { + IList _nodeDepth = policyNodes[j]; + + for (int k = 0; k < _nodeDepth.Count; k++) + { + PkixPolicyNode _node = (PkixPolicyNode)_nodeDepth[k]; + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(_node.ValidPolicy)) + { + foreach (object o in _node.Children) + { + _validPolicyNodeSet.Add(o); + } + } + } + } + + foreach (PkixPolicyNode _node in _validPolicyNodeSet) + { + string _validPolicy = _node.ValidPolicy; + + if (!acceptablePolicies.Contains(_validPolicy)) + { + // TODO? + // validPolicyTree = + // removePolicyNode(validPolicyTree, policyNodes, + // _node); + } + } + if (validPolicyTree != null) + { + for (int j = (n - 1); j >= 0; j--) + { + IList nodes = policyNodes[j]; + + for (int k = 0; k < nodes.Count; k++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[k]; + if (!node.HasChildren) + { + validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(validPolicyTree, + policyNodes, node); + } + } + } + } + } + } + + intersection = validPolicyTree; + } + else + { + // + // (g) (iii) + // + // This implementation is not exactly same as the one described in + // RFC3280. + // However, as far as the validation result is concerned, both + // produce + // adequate result. The only difference is whether AnyPolicy is + // remain + // in the policy tree or not. + // + // (g) (iii) 1 + // + ISet _validPolicyNodeSet = new HashSet(); + + for (int j = 0; j < policyNodes.Length; j++) + { + IList _nodeDepth = policyNodes[j]; + + for (int k = 0; k < _nodeDepth.Count; k++) + { + PkixPolicyNode _node = (PkixPolicyNode)_nodeDepth[k]; + + if (Rfc3280CertPathUtilities.ANY_POLICY.Equals(_node.ValidPolicy)) + { + foreach (PkixPolicyNode _c_node in _node.Children) + { + if (!Rfc3280CertPathUtilities.ANY_POLICY.Equals(_c_node.ValidPolicy)) + { + _validPolicyNodeSet.Add(_c_node); + } + } + } + } + } + + // + // (g) (iii) 2 + // + IEnumerator _vpnsIter = _validPolicyNodeSet.GetEnumerator(); + while (_vpnsIter.MoveNext()) + { + PkixPolicyNode _node = (PkixPolicyNode)_vpnsIter.Current; + string _validPolicy = _node.ValidPolicy; + + if (!userInitialPolicySet.Contains(_validPolicy)) + { + validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(validPolicyTree, policyNodes, _node); + } + } + + // + // (g) (iii) 4 + // + if (validPolicyTree != null) + { + for (int j = (n - 1); j >= 0; j--) + { + IList nodes = policyNodes[j]; + + for (int k = 0; k < nodes.Count; k++) + { + PkixPolicyNode node = (PkixPolicyNode)nodes[k]; + if (!node.HasChildren) + { + validPolicyTree = PkixCertPathValidatorUtilities.RemovePolicyNode(validPolicyTree, policyNodes, + node); + } + } + } + } + + intersection = validPolicyTree; + } + return intersection; + } + + /** + * If use-deltas is set, verify the issuer and scope of the delta CRL. + * + * @param deltaCRL The delta CRL. + * @param completeCRL The complete CRL. + * @param pkixParams The PKIX paramaters. + * @throws AnnotatedException if an exception occurs. + */ + internal static void ProcessCrlC( + X509Crl deltaCRL, + X509Crl completeCRL, + PkixParameters pkixParams) + { + if (deltaCRL == null) + return; + + IssuingDistributionPoint completeidp = null; + try + { + completeidp = IssuingDistributionPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(completeCRL, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception("000 Issuing distribution point extension could not be decoded.", e); + } + + if (pkixParams.IsUseDeltasEnabled) + { + // (c) (1) + if (!deltaCRL.IssuerDN.Equivalent(completeCRL.IssuerDN, true)) + throw new Exception("Complete CRL issuer does not match delta CRL issuer."); + + // (c) (2) + IssuingDistributionPoint deltaidp = null; + try + { + deltaidp = IssuingDistributionPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(deltaCRL, X509Extensions.IssuingDistributionPoint)); + } + catch (Exception e) + { + throw new Exception( + "Issuing distribution point extension from delta CRL could not be decoded.", e); + } + + if (!Platform.Equals(completeidp, deltaidp)) + { + throw new Exception( + "Issuing distribution point extension from delta CRL and complete CRL does not match."); + } + + // (c) (3) + Asn1Object completeKeyIdentifier = null; + try + { + completeKeyIdentifier = PkixCertPathValidatorUtilities.GetExtensionValue( + completeCRL, X509Extensions.AuthorityKeyIdentifier); + } + catch (Exception e) + { + throw new Exception( + "Authority key identifier extension could not be extracted from complete CRL.", e); + } + + Asn1Object deltaKeyIdentifier = null; + try + { + deltaKeyIdentifier = PkixCertPathValidatorUtilities.GetExtensionValue( + deltaCRL, X509Extensions.AuthorityKeyIdentifier); + } + catch (Exception e) + { + throw new Exception( + "Authority key identifier extension could not be extracted from delta CRL.", e); + } + + if (completeKeyIdentifier == null) + throw new Exception("CRL authority key identifier is null."); + + if (deltaKeyIdentifier == null) + throw new Exception("Delta CRL authority key identifier is null."); + + if (!completeKeyIdentifier.Equals(deltaKeyIdentifier)) + { + throw new Exception( + "Delta CRL authority key identifier does not match complete CRL authority key identifier."); + } + } + } + + internal static void ProcessCrlI( + DateTime validDate, + X509Crl deltacrl, + object cert, + CertStatus certStatus, + PkixParameters pkixParams) + { + if (pkixParams.IsUseDeltasEnabled && deltacrl != null) + { + PkixCertPathValidatorUtilities.GetCertStatus(validDate, deltacrl, cert, certStatus); + } + } + + internal static void ProcessCrlJ( + DateTime validDate, + X509Crl completecrl, + object cert, + CertStatus certStatus) + { + if (certStatus.Status == CertStatus.Unrevoked) + { + PkixCertPathValidatorUtilities.GetCertStatus(validDate, completecrl, cert, certStatus); + } + } + + internal static PkixPolicyNode ProcessCertE( + PkixCertPath certPath, + int index, + PkixPolicyNode validPolicyTree) + { + IList certs = certPath.Certificates; + X509Certificate cert = (X509Certificate)certs[index]; + + // + // (e) + // + Asn1Sequence certPolicies = null; + try + { + certPolicies = DerSequence.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue(cert, X509Extensions.CertificatePolicies)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException("Could not read certificate policies extension from certificate.", + e, certPath, index); + } + if (certPolicies == null) + { + validPolicyTree = null; + } + return validPolicyTree; + } + + internal static readonly string[] CrlReasons = new string[] + { + "unspecified", + "keyCompromise", + "cACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + "unknown", + "removeFromCRL", + "privilegeWithdrawn", + "aACompromise" + }; + } +} diff --git a/src/core/srcbc/pkix/Rfc3281CertPathUtilities.cs b/src/core/srcbc/pkix/Rfc3281CertPathUtilities.cs new file mode 100644 index 0000000..19f7d92 --- /dev/null +++ b/src/core/srcbc/pkix/Rfc3281CertPathUtilities.cs @@ -0,0 +1,608 @@ +using System; +using System.Collections; +using System.Globalization; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + internal class Rfc3281CertPathUtilities + { + internal static void ProcessAttrCert7( + IX509AttributeCertificate attrCert, + PkixCertPath certPath, + PkixCertPath holderCertPath, + PkixParameters pkixParams) + { + // TODO: + // AA Controls + // Attribute encryption + // Proxy + ISet critExtOids = attrCert.GetCriticalExtensionOids(); + + // 7.1 + // process extensions + + // target information checked in step 6 / X509AttributeCertStoreSelector + if (critExtOids.Contains(X509Extensions.TargetInformation.Id)) + { + try + { + TargetInformation.GetInstance(PkixCertPathValidatorUtilities + .GetExtensionValue(attrCert, X509Extensions.TargetInformation)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Target information extension could not be read.", e); + } + } + critExtOids.Remove(X509Extensions.TargetInformation.Id); + foreach (PkixAttrCertChecker checker in pkixParams.GetAttrCertCheckers()) + { + checker.Check(attrCert, certPath, holderCertPath, critExtOids); + } + if (!critExtOids.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Attribute certificate contains unsupported critical extensions: " + + critExtOids); + } + } + + /** + * Checks if an attribute certificate is revoked. + * + * @param attrCert Attribute certificate to check if it is revoked. + * @param paramsPKIX PKIX parameters. + * @param issuerCert The issuer certificate of the attribute certificate + * attrCert. + * @param validDate The date when the certificate revocation status should + * be checked. + * @param certPathCerts The certificates of the certification path to be + * checked. + * + * @throws CertPathValidatorException if the certificate is revoked or the + * status cannot be checked or some error occurs. + */ + internal static void CheckCrls( + IX509AttributeCertificate attrCert, + PkixParameters paramsPKIX, + X509Certificate issuerCert, + DateTime validDate, + IList certPathCerts) + { + if (paramsPKIX.IsRevocationEnabled) + { + // check if revocation is available + if (attrCert.GetExtensionValue(X509Extensions.NoRevAvail) == null) + { + CrlDistPoint crldp = null; + try + { + crldp = CrlDistPoint.GetInstance( + PkixCertPathValidatorUtilities.GetExtensionValue( + attrCert, X509Extensions.CrlDistributionPoints)); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "CRL distribution point extension could not be read.", e); + } + try + { + PkixCertPathValidatorUtilities + .AddAdditionalStoresFromCrlDistributionPoint(crldp, paramsPKIX); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "No additional CRL locations could be decoded from CRL distribution point extension.", e); + } + CertStatus certStatus = new CertStatus(); + ReasonsMask reasonsMask = new ReasonsMask(); + + Exception lastException = null; + bool validCrlFound = false; + // for each distribution point + if (crldp != null) + { + DistributionPoint[] dps = null; + try + { + dps = crldp.GetDistributionPoints(); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Distribution points could not be read.", e); + } + try + { + for (int i = 0; i < dps.Length + && certStatus.Status == CertStatus.Unrevoked + && !reasonsMask.IsAllReasons; i++) + { + PkixParameters paramsPKIXClone = (PkixParameters) paramsPKIX + .Clone(); + CheckCrl(dps[i], attrCert, paramsPKIXClone, + validDate, issuerCert, certStatus, reasonsMask, + certPathCerts); + validCrlFound = true; + } + } + catch (Exception e) + { + lastException = new Exception( + "No valid CRL for distribution point found.", e); + } + } + + /* + * If the revocation status has not been determined, repeat the + * process above with any available CRLs not specified in a + * distribution point but issued by the certificate issuer. + */ + + if (certStatus.Status == CertStatus.Unrevoked + && !reasonsMask.IsAllReasons) + { + try + { + /* + * assume a DP with both the reasons and the cRLIssuer + * fields omitted and a distribution point name of the + * certificate issuer. + */ + Asn1Object issuer = null; + try + { + issuer = new Asn1InputStream( + attrCert.Issuer.GetPrincipals()[0].GetEncoded()).ReadObject(); + } + catch (Exception e) + { + throw new Exception( + "Issuer from certificate for CRL could not be reencoded.", + e); + } + DistributionPoint dp = new DistributionPoint( + new DistributionPointName(0, new GeneralNames( + new GeneralName(GeneralName.DirectoryName, issuer))), null, null); + PkixParameters paramsPKIXClone = (PkixParameters) paramsPKIX.Clone(); + CheckCrl(dp, attrCert, paramsPKIXClone, validDate, + issuerCert, certStatus, reasonsMask, certPathCerts); + validCrlFound = true; + } + catch (Exception e) + { + lastException = new Exception( + "No valid CRL for distribution point found.", e); + } + } + + if (!validCrlFound) + { + throw new PkixCertPathValidatorException( + "No valid CRL found.", lastException); + } + if (certStatus.Status != CertStatus.Unrevoked) + { + // TODO This format is forced by the NistCertPath tests + string formattedDate = certStatus.RevocationDate.Value.ToString( + "G", CultureInfo.CreateSpecificCulture("en-us")); + string message = "Attribute certificate revocation after " + + formattedDate; + message += ", reason: " + + Rfc3280CertPathUtilities.CrlReasons[certStatus.Status]; + throw new PkixCertPathValidatorException(message); + } + if (!reasonsMask.IsAllReasons + && certStatus.Status == CertStatus.Unrevoked) + { + certStatus.Status = CertStatus.Undetermined; + } + if (certStatus.Status == CertStatus.Undetermined) + { + throw new PkixCertPathValidatorException( + "Attribute certificate status could not be determined."); + } + + } + else + { + if (attrCert.GetExtensionValue(X509Extensions.CrlDistributionPoints) != null + || attrCert.GetExtensionValue(X509Extensions.AuthorityInfoAccess) != null) + { + throw new PkixCertPathValidatorException( + "No rev avail extension is set, but also an AC revocation pointer."); + } + } + } + } + + internal static void AdditionalChecks( + IX509AttributeCertificate attrCert, + PkixParameters pkixParams) + { + // 1 + foreach (string oid in pkixParams.GetProhibitedACAttributes()) + { + if (attrCert.GetAttributes(oid) != null) + { + throw new PkixCertPathValidatorException( + "Attribute certificate contains prohibited attribute: " + + oid + "."); + } + } + foreach (string oid in pkixParams.GetNecessaryACAttributes()) + { + if (attrCert.GetAttributes(oid) == null) + { + throw new PkixCertPathValidatorException( + "Attribute certificate does not contain necessary attribute: " + + oid + "."); + } + } + } + + internal static void ProcessAttrCert5( + IX509AttributeCertificate attrCert, + PkixParameters pkixParams) + { + try + { + attrCert.CheckValidity(PkixCertPathValidatorUtilities.GetValidDate(pkixParams)); + } + catch (CertificateExpiredException e) + { + throw new PkixCertPathValidatorException( + "Attribute certificate is not valid.", e); + } + catch (CertificateNotYetValidException e) + { + throw new PkixCertPathValidatorException( + "Attribute certificate is not valid.", e); + } + } + + internal static void ProcessAttrCert4( + X509Certificate acIssuerCert, + PkixParameters pkixParams) + { + ISet set = pkixParams.GetTrustedACIssuers(); + bool trusted = false; + foreach (TrustAnchor anchor in set) + { + if (acIssuerCert.SubjectDN.ToString(false, X509Name.RFC2253Symbols) + .Equals(anchor.CAName) + || acIssuerCert.Equals(anchor.TrustedCert)) + { + trusted = true; + } + } + if (!trusted) + { + throw new PkixCertPathValidatorException( + "Attribute certificate issuer is not directly trusted."); + } + } + + internal static void ProcessAttrCert3( + X509Certificate acIssuerCert, + PkixParameters pkixParams) + { + if (acIssuerCert.GetKeyUsage() != null + && (!acIssuerCert.GetKeyUsage()[0] && !acIssuerCert.GetKeyUsage()[1])) + { + throw new PkixCertPathValidatorException( + "Attribute certificate issuer public key cannot be used to validate digital signatures."); + } + if (acIssuerCert.GetBasicConstraints() != -1) + { + throw new PkixCertPathValidatorException( + "Attribute certificate issuer is also a public key certificate issuer."); + } + } + + internal static PkixCertPathValidatorResult ProcessAttrCert2( + PkixCertPath certPath, + PkixParameters pkixParams) + { + PkixCertPathValidator validator = new PkixCertPathValidator(); + + try + { + return validator.Validate(certPath, pkixParams); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException( + "Certification path for issuer certificate of attribute certificate could not be validated.", + e); + } + } + + /** + * Searches for a holder public key certificate and verifies its + * certification path. + * + * @param attrCert the attribute certificate. + * @param pkixParams The PKIX parameters. + * @return The certificate path of the holder certificate. + * @throws Exception if + *
    + *
  • no public key certificate can be found although holder + * information is given by an entity name or a base certificate + * ID
  • + *
  • support classes cannot be created
  • + *
  • no certification path for the public key certificate can + * be built
  • + *
+ */ + internal static PkixCertPath ProcessAttrCert1( + IX509AttributeCertificate attrCert, + PkixParameters pkixParams) + { + PkixCertPathBuilderResult result = null; + // find holder PKCs + ISet holderPKCs = new HashSet(); + if (attrCert.Holder.GetIssuer() != null) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + selector.SerialNumber = attrCert.Holder.SerialNumber; + X509Name[] principals = attrCert.Holder.GetIssuer(); + for (int i = 0; i < principals.Length; i++) + { + try + { +// if (principals[i] is X500Principal) + { + selector.Issuer = principals[i]; + } + holderPKCs.AddAll(PkixCertPathValidatorUtilities + .FindCertificates(selector, pkixParams.GetStores())); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + if (holderPKCs.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Public key certificate specified in base certificate ID for attribute certificate cannot be found."); + } + } + if (attrCert.Holder.GetEntityNames() != null) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + X509Name[] principals = attrCert.Holder.GetEntityNames(); + for (int i = 0; i < principals.Length; i++) + { + try + { +// if (principals[i] is X500Principal) + { + selector.Issuer = principals[i]; + } + holderPKCs.AddAll(PkixCertPathValidatorUtilities + .FindCertificates(selector, pkixParams.GetStores())); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + if (holderPKCs.IsEmpty) + { + throw new PkixCertPathValidatorException( + "Public key certificate specified in entity name for attribute certificate cannot be found."); + } + } + + // verify cert paths for PKCs + PkixBuilderParameters parameters = (PkixBuilderParameters) + PkixBuilderParameters.GetInstance(pkixParams); + + PkixCertPathValidatorException lastException = null; + foreach (X509Certificate cert in holderPKCs) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + selector.Certificate = cert; + parameters.SetTargetConstraints(selector); + + PkixCertPathBuilder builder = new PkixCertPathBuilder(); + + try + { + result = builder.Build(PkixBuilderParameters.GetInstance(parameters)); + } + catch (PkixCertPathBuilderException e) + { + lastException = new PkixCertPathValidatorException( + "Certification path for public key certificate of attribute certificate could not be build.", + e); + } + } + if (lastException != null) + { + throw lastException; + } + return result.CertPath; + } + + /** + * + * Checks a distribution point for revocation information for the + * certificate attrCert. + * + * @param dp The distribution point to consider. + * @param attrCert The attribute certificate which should be checked. + * @param paramsPKIX PKIX parameters. + * @param validDate The date when the certificate revocation status should + * be checked. + * @param issuerCert Certificate to check if it is revoked. + * @param reasonMask The reasons mask which is already checked. + * @param certPathCerts The certificates of the certification path to be + * checked. + * @throws Exception if the certificate is revoked or the status + * cannot be checked or some error occurs. + */ + private static void CheckCrl( + DistributionPoint dp, + IX509AttributeCertificate attrCert, + PkixParameters paramsPKIX, + DateTime validDate, + X509Certificate issuerCert, + CertStatus certStatus, + ReasonsMask reasonMask, + IList certPathCerts) + { + /* + * 4.3.6 No Revocation Available + * + * The noRevAvail extension, defined in [X.509-2000], allows an AC + * issuer to indicate that no revocation information will be made + * available for this AC. + */ + if (attrCert.GetExtensionValue(X509Extensions.NoRevAvail) != null) + { + return; + } + + DateTime currentDate = DateTime.UtcNow; + if (validDate.CompareTo(currentDate) > 0) + { + throw new Exception("Validation time is in future."); + } + + // (a) + /* + * We always get timely valid CRLs, so there is no step (a) (1). + * "locally cached" CRLs are assumed to be in getStore(), additional + * CRLs must be enabled in the ExtendedPkixParameters and are in + * getAdditionalStore() + */ + ISet crls = PkixCertPathValidatorUtilities.GetCompleteCrls(dp, attrCert, + currentDate, paramsPKIX); + bool validCrlFound = false; + Exception lastException = null; + + IEnumerator crl_iter = crls.GetEnumerator(); + + while (crl_iter.MoveNext() + && certStatus.Status == CertStatus.Unrevoked + && !reasonMask.IsAllReasons) + { + try + { + X509Crl crl = (X509Crl) crl_iter.Current; + + // (d) + ReasonsMask interimReasonsMask = Rfc3280CertPathUtilities.ProcessCrlD(crl, dp); + + // (e) + /* + * The reasons mask is updated at the end, so only valid CRLs + * can update it. If this CRL does not contain new reasons it + * must be ignored. + */ + if (!interimReasonsMask.HasNewReasons(reasonMask)) + { + continue; + } + + // (f) + ISet keys = Rfc3280CertPathUtilities.ProcessCrlF(crl, attrCert, + null, null, paramsPKIX, certPathCerts); + // (g) + AsymmetricKeyParameter pubKey = Rfc3280CertPathUtilities.ProcessCrlG(crl, keys); + + X509Crl deltaCRL = null; + + if (paramsPKIX.IsUseDeltasEnabled) + { + // get delta CRLs + ISet deltaCRLs = PkixCertPathValidatorUtilities.GetDeltaCrls( + currentDate, paramsPKIX, crl); + // we only want one valid delta CRL + // (h) + deltaCRL = Rfc3280CertPathUtilities.ProcessCrlH(deltaCRLs, pubKey); + } + + /* + * CRL must be be valid at the current time, not the validation + * time. If a certificate is revoked with reason keyCompromise, + * cACompromise, it can be used for forgery, also for the past. + * This reason may not be contained in older CRLs. + */ + + /* + * in the chain model signatures stay valid also after the + * certificate has been expired, so they do not have to be in + * the CRL vality time + */ + if (paramsPKIX.ValidityModel != PkixParameters.ChainValidityModel) + { + /* + * if a certificate has expired, but was revoked, it is not + * more in the CRL, so it would be regarded as valid if the + * first check is not done + */ + if (attrCert.NotAfter.CompareTo(crl.ThisUpdate) < 0) + { + throw new Exception( + "No valid CRL for current time found."); + } + } + + Rfc3280CertPathUtilities.ProcessCrlB1(dp, attrCert, crl); + + // (b) (2) + Rfc3280CertPathUtilities.ProcessCrlB2(dp, attrCert, crl); + + // (c) + Rfc3280CertPathUtilities.ProcessCrlC(deltaCRL, crl, paramsPKIX); + + // (i) + Rfc3280CertPathUtilities.ProcessCrlI(validDate, deltaCRL, + attrCert, certStatus, paramsPKIX); + + // (j) + Rfc3280CertPathUtilities.ProcessCrlJ(validDate, crl, attrCert, + certStatus); + + // (k) + if (certStatus.Status == CrlReason.RemoveFromCrl) + { + certStatus.Status = CertStatus.Unrevoked; + } + + // update reasons mask + reasonMask.AddReasons(interimReasonsMask); + validCrlFound = true; + } + catch (Exception e) + { + lastException = e; + } + } + if (!validCrlFound) + { + throw lastException; + } + } + } +} diff --git a/src/core/srcbc/pkix/TrustAnchor.cs b/src/core/srcbc/pkix/TrustAnchor.cs new file mode 100644 index 0000000..86bc42d --- /dev/null +++ b/src/core/srcbc/pkix/TrustAnchor.cs @@ -0,0 +1,259 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + /// + /// A trust anchor or most-trusted Certification Authority (CA). + /// + /// This class represents a "most-trusted CA", which is used as a trust anchor + /// for validating X.509 certification paths. A most-trusted CA includes the + /// public key of the CA, the CA's name, and any constraints upon the set of + /// paths which may be validated using this key. These parameters can be + /// specified in the form of a trusted X509Certificate or as individual + /// parameters. + /// + public class TrustAnchor + { + private readonly AsymmetricKeyParameter pubKey; + private readonly string caName; + private readonly X509Name caPrincipal; + private readonly X509Certificate trustedCert; + private byte[] ncBytes; + private NameConstraints nc; + + /// + /// Creates an instance of TrustAnchor with the specified X509Certificate and + /// optional name constraints, which are intended to be used as additional + /// constraints when validating an X.509 certification path. + /// The name constraints are specified as a byte array. This byte array + /// should contain the DER encoded form of the name constraints, as they + /// would appear in the NameConstraints structure defined in RFC 2459 and + /// X.509. The ASN.1 definition of this structure appears below. + /// + ///
+	    ///	NameConstraints ::= SEQUENCE {
+	    ///		permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
+	    ///		excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
+	    ///	   
+        /// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
+        /// 
+        ///		GeneralSubtree ::= SEQUENCE {
+        ///		base                    GeneralName,
+        ///		minimum         [0]     BaseDistance DEFAULT 0,
+        ///		maximum         [1]     BaseDistance OPTIONAL }
+        ///		
+        ///		BaseDistance ::= INTEGER (0..MAX)
+		///
+		///		GeneralName ::= CHOICE {
+		///		otherName                       [0]     OtherName,
+		///		rfc822Name                      [1]     IA5String,
+		///		dNSName                         [2]     IA5String,
+		///		x400Address                     [3]     ORAddress,
+		///		directoryName                   [4]     Name,
+		///		ediPartyName                    [5]     EDIPartyName,
+		///		uniformResourceIdentifier       [6]     IA5String,
+		///		iPAddress                       [7]     OCTET STRING,
+		///		registeredID                    [8]     OBJECT IDENTIFIER}
+		///	
+ /// + /// Note that the name constraints byte array supplied is cloned to protect + /// against subsequent modifications. + ///
+ /// a trusted X509Certificate + /// a byte array containing the ASN.1 DER encoding of a + /// NameConstraints extension to be used for checking name + /// constraints. Only the value of the extension is included, not + /// the OID or criticality flag. Specify null to omit the + /// parameter. + /// if the specified X509Certificate is null + public TrustAnchor( + X509Certificate trustedCert, + byte[] nameConstraints) + { + if (trustedCert == null) + throw new ArgumentNullException("trustedCert"); + + this.trustedCert = trustedCert; + this.pubKey = null; + this.caName = null; + this.caPrincipal = null; + setNameConstraints(nameConstraints); + } + + /// + /// Creates an instance of TrustAnchor where the + /// most-trusted CA is specified as an X500Principal and public key. + /// + /// + ///

+ /// Name constraints are an optional parameter, and are intended to be used + /// as additional constraints when validating an X.509 certification path. + ///

+ /// The name constraints are specified as a byte array. This byte array + /// contains the DER encoded form of the name constraints, as they + /// would appear in the NameConstraints structure defined in RFC 2459 + /// and X.509. The ASN.1 notation for this structure is supplied in the + /// documentation for the other constructors. + ///

+ /// Note that the name constraints byte array supplied here is cloned to + /// protect against subsequent modifications. + ///

+ ///
+ /// the name of the most-trusted CA as X509Name + /// the public key of the most-trusted CA + /// + /// a byte array containing the ASN.1 DER encoding of a NameConstraints extension to + /// be used for checking name constraints. Only the value of the extension is included, + /// not the OID or criticality flag. Specify null to omit the parameter. + /// + /// + /// if caPrincipal or pubKey is null + /// + public TrustAnchor( + X509Name caPrincipal, + AsymmetricKeyParameter pubKey, + byte[] nameConstraints) + { + if (caPrincipal == null) + throw new ArgumentNullException("caPrincipal"); + if (pubKey == null) + throw new ArgumentNullException("pubKey"); + + this.trustedCert = null; + this.caPrincipal = caPrincipal; + this.caName = caPrincipal.ToString(); + this.pubKey = pubKey; + setNameConstraints(nameConstraints); + } + + /// + /// Creates an instance of TrustAnchor where the most-trusted + /// CA is specified as a distinguished name and public key. Name constraints + /// are an optional parameter, and are intended to be used as additional + /// constraints when validating an X.509 certification path. + ///
+ /// The name constraints are specified as a byte array. This byte array + /// contains the DER encoded form of the name constraints, as they would + /// appear in the NameConstraints structure defined in RFC 2459 and X.509. + ///
+ /// the X.500 distinguished name of the most-trusted CA in RFC + /// 2253 string format + /// the public key of the most-trusted CA + /// a byte array containing the ASN.1 DER encoding of a + /// NameConstraints extension to be used for checking name + /// constraints. Only the value of the extension is included, not + /// the OID or criticality flag. Specify null to omit the + /// parameter. + /// throws NullPointerException, IllegalArgumentException + public TrustAnchor( + string caName, + AsymmetricKeyParameter pubKey, + byte[] nameConstraints) + { + if (caName == null) + throw new ArgumentNullException("caName"); + if (pubKey == null) + throw new ArgumentNullException("pubKey"); + if (caName.Length == 0) + throw new ArgumentException("caName can not be an empty string"); + + this.caPrincipal = new X509Name(caName); + this.pubKey = pubKey; + this.caName = caName; + this.trustedCert = null; + setNameConstraints(nameConstraints); + } + + /// + /// Returns the most-trusted CA certificate. + /// + public X509Certificate TrustedCert + { + get { return this.trustedCert; } + } + + /// + /// Returns the name of the most-trusted CA as an X509Name. + /// + public X509Name CA + { + get { return this.caPrincipal; } + } + + /// + /// Returns the name of the most-trusted CA in RFC 2253 string format. + /// + public string CAName + { + get { return this.caName; } + } + + /// + /// Returns the public key of the most-trusted CA. + /// + public AsymmetricKeyParameter CAPublicKey + { + get { return this.pubKey; } + } + + /// + /// Decode the name constraints and clone them if not null. + /// + private void setNameConstraints( + byte[] bytes) + { + if (bytes == null) + { + ncBytes = null; + nc = null; + } + else + { + ncBytes = (byte[]) bytes.Clone(); + // validate DER encoding + //nc = new NameConstraintsExtension(Boolean.FALSE, bytes); + nc = NameConstraints.GetInstance(Asn1Object.FromByteArray(bytes)); + } + } + + public byte[] GetNameConstraints + { + get { return Arrays.Clone(ncBytes); } + } + + /// + /// Returns a formatted string describing the TrustAnchor. + /// + /// a formatted string describing the TrustAnchor + public override string ToString() + { + // TODO Some of the sub-objects might not implement ToString() properly + string nl = Platform.NewLine; + StringBuilder sb = new StringBuilder(); + sb.Append("["); + sb.Append(nl); + if (this.pubKey != null) + { + sb.Append(" Trusted CA Public Key: ").Append(this.pubKey).Append(nl); + sb.Append(" Trusted CA Issuer Name: ").Append(this.caName).Append(nl); + } + else + { + sb.Append(" Trusted CA cert: ").Append(this.TrustedCert).Append(nl); + } + if (nc != null) + { + sb.Append(" Name Constraints: ").Append(nc).Append(nl); + } + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/core/srcbc/security/PrivateKeyFactory.cs b/src/core/srcbc/security/PrivateKeyFactory.cs index 0235c23..7ace163 100644 --- a/src/core/srcbc/security/PrivateKeyFactory.cs +++ b/src/core/srcbc/security/PrivateKeyFactory.cs @@ -1,210 +1,212 @@ -using System; -using System.Collections; -using System.IO; -using System.Text; - -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.CryptoPro; -using Org.BouncyCastle.Asn1.Nist; -using Org.BouncyCastle.Asn1.Oiw; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.Sec; -using Org.BouncyCastle.Asn1.TeleTrust; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Pkcs; - -namespace Org.BouncyCastle.Security -{ - public sealed class PrivateKeyFactory - { - private PrivateKeyFactory() - { - } - - public static AsymmetricKeyParameter CreateKey( - byte[] privateKeyInfoData) - { - return CreateKey( - PrivateKeyInfo.GetInstance( - Asn1Object.FromByteArray(privateKeyInfoData))); - } - - public static AsymmetricKeyParameter CreateKey( - Stream inStr) - { - return CreateKey( - PrivateKeyInfo.GetInstance( - Asn1Object.FromStream(inStr))); - } - - public static AsymmetricKeyParameter CreateKey( - PrivateKeyInfo keyInfo) - { - AlgorithmIdentifier algID = keyInfo.AlgorithmID; - DerObjectIdentifier algOid = algID.ObjectID; - - if (algOid.Equals(PkcsObjectIdentifiers.RsaEncryption)) - { - RsaPrivateKeyStructure keyStructure = new RsaPrivateKeyStructure( - Asn1Sequence.GetInstance(keyInfo.PrivateKey)); - - return new RsaPrivateCrtKeyParameters( - keyStructure.Modulus, - keyStructure.PublicExponent, - keyStructure.PrivateExponent, - keyStructure.Prime1, - keyStructure.Prime2, - keyStructure.Exponent1, - keyStructure.Exponent2, - keyStructure.Coefficient); - } - else if (algOid.Equals(PkcsObjectIdentifiers.DhKeyAgreement)) - { - DHParameter para = new DHParameter( - Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); - DerInteger derX = (DerInteger)keyInfo.PrivateKey; - - return new DHPrivateKeyParameters( - derX.Value, - new DHParameters(para.P, para.G)); - } - else if (algOid.Equals(OiwObjectIdentifiers.ElGamalAlgorithm)) - { - ElGamalParameter para = new ElGamalParameter( - Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); - DerInteger derX = (DerInteger)keyInfo.PrivateKey; - - return new ElGamalPrivateKeyParameters( - derX.Value, - new ElGamalParameters(para.P, para.G)); - } - else if (algOid.Equals(X9ObjectIdentifiers.IdDsa)) - { - DerInteger derX = (DerInteger) keyInfo.PrivateKey; - Asn1Encodable ae = algID.Parameters; - - DsaParameters parameters = null; - if (ae != null) - { - DsaParameter para = DsaParameter.GetInstance(ae.ToAsn1Object()); - parameters = new DsaParameters(para.P, para.Q, para.G); - } - - return new DsaPrivateKeyParameters(derX.Value, parameters); - } - else if (algOid.Equals(X9ObjectIdentifiers.IdECPublicKey)) - { - X962Parameters para = new X962Parameters(algID.Parameters.ToAsn1Object()); - X9ECParameters ecP; - - if (para.IsNamedCurve) - { - // TODO ECGost3410NamedCurves support (returns ECDomainParameters though) - - DerObjectIdentifier oid = (DerObjectIdentifier) para.Parameters; - ecP = X962NamedCurves.GetByOid(oid); - - if (ecP == null) - { - ecP = SecNamedCurves.GetByOid(oid); - - if (ecP == null) - { - ecP = NistNamedCurves.GetByOid(oid); - - if (ecP == null) - { - ecP = TeleTrusTNamedCurves.GetByOid(oid); - } - } - } - } - else - { - ecP = new X9ECParameters((Asn1Sequence) para.Parameters); - } - - ECDomainParameters dParams = new ECDomainParameters( - ecP.Curve, - ecP.G, - ecP.N, - ecP.H, - ecP.GetSeed()); - - ECPrivateKeyStructure ec = new ECPrivateKeyStructure( - Asn1Sequence.GetInstance(keyInfo.PrivateKey)); - - return new ECPrivateKeyParameters(ec.GetKey(), dParams); - } - else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x2001)) - { - Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( - Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); - - ECPrivateKeyStructure ec = new ECPrivateKeyStructure( - Asn1Sequence.GetInstance(keyInfo.PrivateKey)); - - ECDomainParameters ecP = ECGost3410NamedCurves.GetByOid(gostParams.PublicKeyParamSet); - - if (ecP == null) - return null; - - return new ECPrivateKeyParameters(ec.GetKey(), gostParams.PublicKeyParamSet); - } - else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x94)) - { - Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( - Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); - - DerOctetString derX = (DerOctetString) keyInfo.PrivateKey; - byte[] keyEnc = derX.GetOctets(); - byte[] keyBytes = new byte[keyEnc.Length]; - - for (int i = 0; i != keyEnc.Length; i++) - { - keyBytes[i] = keyEnc[keyEnc.Length - 1 - i]; // was little endian - } - - BigInteger x = new BigInteger(1, keyBytes); - - return new Gost3410PrivateKeyParameters(x, gostParams.PublicKeyParamSet); - } - else - { - throw new SecurityUtilityException("algorithm identifier in key not recognised"); - } - } - - public static AsymmetricKeyParameter DecryptKey( - char[] passPhrase, - EncryptedPrivateKeyInfo encInfo) - { - return CreateKey(PrivateKeyInfoFactory.CreatePrivateKeyInfo(passPhrase, encInfo)); - } - - public static AsymmetricKeyParameter DecryptKey( - char[] passPhrase, - byte[] encryptedPrivateKeyInfoData) - { - return DecryptKey(passPhrase, Asn1Object.FromByteArray(encryptedPrivateKeyInfoData)); - } - - public static AsymmetricKeyParameter DecryptKey( - char[] passPhrase, - Stream encryptedPrivateKeyInfoStream) - { - return DecryptKey(passPhrase, Asn1Object.FromStream(encryptedPrivateKeyInfoStream)); - } - - private static AsymmetricKeyParameter DecryptKey( - char[] passPhrase, - Asn1Object asn1Object) - { - return DecryptKey(passPhrase, EncryptedPrivateKeyInfo.GetInstance(asn1Object)); - } - } -} +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.CryptoPro; +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Asn1.Oiw; +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.TeleTrust; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; + +namespace Org.BouncyCastle.Security +{ + public sealed class PrivateKeyFactory + { + private PrivateKeyFactory() + { + } + + public static AsymmetricKeyParameter CreateKey( + byte[] privateKeyInfoData) + { + return CreateKey( + PrivateKeyInfo.GetInstance( + Asn1Object.FromByteArray(privateKeyInfoData))); + } + + public static AsymmetricKeyParameter CreateKey( + Stream inStr) + { + return CreateKey( + PrivateKeyInfo.GetInstance( + Asn1Object.FromStream(inStr))); + } + + public static AsymmetricKeyParameter CreateKey( + PrivateKeyInfo keyInfo) + { + AlgorithmIdentifier algID = keyInfo.AlgorithmID; + DerObjectIdentifier algOid = algID.ObjectID; + + if (algOid.Equals(PkcsObjectIdentifiers.RsaEncryption)) + { + RsaPrivateKeyStructure keyStructure = new RsaPrivateKeyStructure( + Asn1Sequence.GetInstance(keyInfo.PrivateKey)); + + return new RsaPrivateCrtKeyParameters( + keyStructure.Modulus, + keyStructure.PublicExponent, + keyStructure.PrivateExponent, + keyStructure.Prime1, + keyStructure.Prime2, + keyStructure.Exponent1, + keyStructure.Exponent2, + keyStructure.Coefficient); + } + else if (algOid.Equals(PkcsObjectIdentifiers.DhKeyAgreement)) + { + DHParameter para = new DHParameter( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + DerInteger derX = (DerInteger)keyInfo.PrivateKey; + + BigInteger lVal = para.L; + int l = lVal == null ? 0 : lVal.IntValue; + DHParameters dhParams = new DHParameters(para.P, para.G, null, l); + + return new DHPrivateKeyParameters(derX.Value, dhParams); + } + else if (algOid.Equals(OiwObjectIdentifiers.ElGamalAlgorithm)) + { + ElGamalParameter para = new ElGamalParameter( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + DerInteger derX = (DerInteger)keyInfo.PrivateKey; + + return new ElGamalPrivateKeyParameters( + derX.Value, + new ElGamalParameters(para.P, para.G)); + } + else if (algOid.Equals(X9ObjectIdentifiers.IdDsa)) + { + DerInteger derX = (DerInteger) keyInfo.PrivateKey; + Asn1Encodable ae = algID.Parameters; + + DsaParameters parameters = null; + if (ae != null) + { + DsaParameter para = DsaParameter.GetInstance(ae.ToAsn1Object()); + parameters = new DsaParameters(para.P, para.Q, para.G); + } + + return new DsaPrivateKeyParameters(derX.Value, parameters); + } + else if (algOid.Equals(X9ObjectIdentifiers.IdECPublicKey)) + { + X962Parameters para = new X962Parameters(algID.Parameters.ToAsn1Object()); + X9ECParameters ecP; + + if (para.IsNamedCurve) + { + // TODO ECGost3410NamedCurves support (returns ECDomainParameters though) + + DerObjectIdentifier oid = (DerObjectIdentifier) para.Parameters; + ecP = X962NamedCurves.GetByOid(oid); + + if (ecP == null) + { + ecP = SecNamedCurves.GetByOid(oid); + + if (ecP == null) + { + ecP = NistNamedCurves.GetByOid(oid); + + if (ecP == null) + { + ecP = TeleTrusTNamedCurves.GetByOid(oid); + } + } + } + } + else + { + ecP = new X9ECParameters((Asn1Sequence) para.Parameters); + } + + ECDomainParameters dParams = new ECDomainParameters( + ecP.Curve, + ecP.G, + ecP.N, + ecP.H, + ecP.GetSeed()); + + ECPrivateKeyStructure ec = new ECPrivateKeyStructure( + Asn1Sequence.GetInstance(keyInfo.PrivateKey)); + + return new ECPrivateKeyParameters(ec.GetKey(), dParams); + } + else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x2001)) + { + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + + ECPrivateKeyStructure ec = new ECPrivateKeyStructure( + Asn1Sequence.GetInstance(keyInfo.PrivateKey)); + + ECDomainParameters ecP = ECGost3410NamedCurves.GetByOid(gostParams.PublicKeyParamSet); + + if (ecP == null) + return null; + + return new ECPrivateKeyParameters(ec.GetKey(), gostParams.PublicKeyParamSet); + } + else if (algOid.Equals(CryptoProObjectIdentifiers.GostR3410x94)) + { + Gost3410PublicKeyAlgParameters gostParams = new Gost3410PublicKeyAlgParameters( + Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); + + DerOctetString derX = (DerOctetString) keyInfo.PrivateKey; + byte[] keyEnc = derX.GetOctets(); + byte[] keyBytes = new byte[keyEnc.Length]; + + for (int i = 0; i != keyEnc.Length; i++) + { + keyBytes[i] = keyEnc[keyEnc.Length - 1 - i]; // was little endian + } + + BigInteger x = new BigInteger(1, keyBytes); + + return new Gost3410PrivateKeyParameters(x, gostParams.PublicKeyParamSet); + } + else + { + throw new SecurityUtilityException("algorithm identifier in key not recognised"); + } + } + + public static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + EncryptedPrivateKeyInfo encInfo) + { + return CreateKey(PrivateKeyInfoFactory.CreatePrivateKeyInfo(passPhrase, encInfo)); + } + + public static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + byte[] encryptedPrivateKeyInfoData) + { + return DecryptKey(passPhrase, Asn1Object.FromByteArray(encryptedPrivateKeyInfoData)); + } + + public static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + Stream encryptedPrivateKeyInfoStream) + { + return DecryptKey(passPhrase, Asn1Object.FromStream(encryptedPrivateKeyInfoStream)); + } + + private static AsymmetricKeyParameter DecryptKey( + char[] passPhrase, + Asn1Object asn1Object) + { + return DecryptKey(passPhrase, EncryptedPrivateKeyInfo.GetInstance(asn1Object)); + } + } +} diff --git a/src/core/srcbc/security/PublicKeyFactory.cs b/src/core/srcbc/security/PublicKeyFactory.cs index 775b84e..a38e58e 100644 --- a/src/core/srcbc/security/PublicKeyFactory.cs +++ b/src/core/srcbc/security/PublicKeyFactory.cs @@ -62,7 +62,11 @@ namespace Org.BouncyCastle.Security Asn1Sequence.GetInstance(algID.Parameters.ToAsn1Object())); DerInteger derY = (DerInteger) keyInfo.GetPublicKey(); - return new DHPublicKeyParameters(derY.Value, new DHParameters(para.P, para.G)); + BigInteger lVal = para.L; + int l = lVal == null ? 0 : lVal.IntValue; + DHParameters dhParams = new DHParameters(para.P, para.G, null, l); + + return new DHPublicKeyParameters(derY.Value, dhParams); } else if (algOid.Equals(OiwObjectIdentifiers.ElGamalAlgorithm)) { @@ -119,7 +123,7 @@ namespace Org.BouncyCastle.Security } else { - ecP = new X9ECParameters((Asn1Sequence)para.Parameters.ToAsn1Object()); + ecP = new X9ECParameters((Asn1Sequence)para.Parameters); } ECDomainParameters dParams = new ECDomainParameters( diff --git a/src/core/srcbc/util/Arrays.cs b/src/core/srcbc/util/Arrays.cs index af16246..0a2f69d 100644 --- a/src/core/srcbc/util/Arrays.cs +++ b/src/core/srcbc/util/Arrays.cs @@ -11,6 +11,28 @@ namespace Org.BouncyCastle.Utilities { } + public static bool AreEqual( + bool[] a, + bool[] b) + { + if (a == b) + return true; + + if (a == null || b == null) + return false; + + if (a.Length != b.Length) + return false; + + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) + return false; + } + + return true; + } + /// /// Are two arrays equal. /// diff --git a/src/core/srcbc/util/BigIntegers.cs b/src/core/srcbc/util/BigIntegers.cs index b3c57a0..12d661e 100644 --- a/src/core/srcbc/util/BigIntegers.cs +++ b/src/core/srcbc/util/BigIntegers.cs @@ -1,6 +1,7 @@ using System; using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; namespace Org.BouncyCastle.Utilities { @@ -9,6 +10,8 @@ namespace Org.BouncyCastle.Utilities */ public sealed class BigIntegers { + private const int MaxIterations = 1000; + private BigIntegers() { } @@ -24,5 +27,45 @@ namespace Org.BouncyCastle.Utilities { return n.ToByteArrayUnsigned(); } + + /** + * Return a random BigInteger not less than 'min' and not greater than 'max' + * + * @param min the least value that may be generated + * @param max the greatest value that may be generated + * @param random the source of randomness + * @return a random BigInteger value in the range [min,max] + */ + public static BigInteger CreateRandomInRange( + BigInteger min, + BigInteger max, + SecureRandom random) + { + int cmp = min.CompareTo(max); + if (cmp >= 0) + { + if (cmp > 0) + throw new ArgumentException("'min' may not be greater than 'max'"); + + return min; + } + + if (min.BitLength > max.BitLength / 2) + { + return CreateRandomInRange(BigInteger.Zero, max.Subtract(min), random).Add(min); + } + + for (int i = 0; i < MaxIterations; ++i) + { + BigInteger x = new BigInteger(max.BitLength, random); + if (x.CompareTo(min) >= 0 && x.CompareTo(max) <= 0) + { + return x; + } + } + + // fall back to a faster (restricted) method + return new BigInteger(max.Subtract(min).BitLength - 1, random).Add(min); + } } } diff --git a/src/core/srcbc/x509/X509CertificateParser.cs b/src/core/srcbc/x509/X509CertificateParser.cs index 1811713..dce0757 100644 --- a/src/core/srcbc/x509/X509CertificateParser.cs +++ b/src/core/srcbc/x509/X509CertificateParser.cs @@ -157,7 +157,7 @@ namespace Org.BouncyCastle.X509 } catch (Exception e) { - throw new CertificateException(e.ToString()); + throw new CertificateException("Failed to read certificate", e); } } diff --git a/src/core/srcbc/x509/X509V3CertificateGenerator.cs b/src/core/srcbc/x509/X509V3CertificateGenerator.cs index 443f045..7341782 100644 --- a/src/core/srcbc/x509/X509V3CertificateGenerator.cs +++ b/src/core/srcbc/x509/X509V3CertificateGenerator.cs @@ -129,6 +129,49 @@ namespace Org.BouncyCastle.X509 tbsGen.SetSignature(sigAlgId); } + /// + /// Set the subject unique ID - note: it is very rare that it is correct to do this. + /// + /// + public void SetSubjectUniqueID( + bool[] uniqueID) + { + tbsGen.SetSubjectUniqueID(booleanToBitString(uniqueID)); + } + + /// + /// Set the issuer unique ID - note: it is very rare that it is correct to do this. + /// + /// + public void SetIssuerUniqueID( + bool[] uniqueID) + { + tbsGen.SetIssuerUniqueID(booleanToBitString(uniqueID)); + } + + private DerBitString booleanToBitString( + bool[] id) + { + byte[] bytes = new byte[(id.Length + 7) / 8]; + + for (int i = 0; i != id.Length; i++) + { + if (id[i]) + { + bytes[i / 8] |= (byte)(1 << ((7 - (i % 8)))); + } + } + + int pad = id.Length % 8; + + if (pad == 0) + { + return new DerBitString(bytes); + } + + return new DerBitString(bytes, 8 - pad); + } + /// /// Add a given extension field for the standard extensions tag (tag 3). ///