/// <summary> /// Determines whether the specified RSA parameters /// can be represented in the CAPI format. /// </summary> /// <param name="parameters">The parameters.</param> /// <returns><c>true</c> if CAPI is compatible with these parameters; <c>false</c> otherwise.</returns> public static bool IsCapiCompatible(RSAParameters parameters) { Requires.Argument(parameters.Modulus != null, nameof(parameters), Strings.PropertyXMustBeNonEmpty, nameof(RSAParameters.Modulus)); // Only private keys have this restriction. if (!KeyFormatter.HasPrivateKey(parameters)) { return(true); } int halfModulusLength = (parameters.Modulus.Length + 1) / 2; // These are the same assertions that Windows crypto lib itself // follows when it returns 'bad data'. // CAPI's file format does not include lengths for parameters. // Instead it makes some assumptions about their relative lengths // which make it fundamentally incompatible with some private keys // generated by iOS. return (halfModulusLength == parameters.P?.Length && halfModulusLength == parameters.Q?.Length && halfModulusLength == parameters.DP?.Length && halfModulusLength == parameters.DQ?.Length && halfModulusLength == parameters.InverseQ?.Length && parameters.Modulus.Length == parameters.D?.Length); }
/// <summary> /// Writes the core. /// </summary> /// <param name="stream">The stream.</param> /// <param name="value">The value.</param> protected override void WriteCore(Stream stream, RSAParameters value) { Requires.NotNull(stream, "stream"); var sequence = new MemoryStream(); if (KeyFormatter.HasPrivateKey(value)) { // Only include the version element if this is a private key. sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, new byte[1])); } // Take care to always prepend a zero when an integer starts with a leading bit of 1, // since the ASN.1 spec is that integers are encoded as two's complement, and these integers // are always intended to be interpreted as positive. sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.Modulus))); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.Exponent))); if (KeyFormatter.HasPrivateKey(value)) { sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.D))); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.P))); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.Q))); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.DP))); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.DQ))); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, PrependLeadingZero(value.InverseQ))); } stream.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Constructed, Asn.BerTag.Sequence, sequence.ToArray())); }
/// <summary> /// Reads a key from the specified stream. /// </summary> /// <param name="stream">The stream.</param> /// <returns> /// The RSA Parameters of the key. /// </returns> /// <exception cref="System.ArgumentException"> /// Unexpected format. /// or /// Unexpected format. /// or /// Unexpected algorithm. /// or /// Unexpected format. /// </exception> protected override RSAParameters ReadCore(Stream stream) { var sequence = stream.ReadAsn1Elements().First(); if (sequence.Class != Asn.BerClass.Universal || sequence.PC != Asn.BerPC.Constructed || sequence.Tag != Asn.BerTag.Sequence) { throw new ArgumentException("Unexpected format."); } var elements = Asn.ReadAsn1Elements(sequence.Content).ToList(); if (elements.Count != 2 || elements[0].Class != Asn.BerClass.Universal || elements[0].PC != Asn.BerPC.Constructed || elements[0].Tag != Asn.BerTag.Sequence) { throw new ArgumentException("Unexpected format."); } var oid = Asn.ReadAsn1Elements(elements[0].Content).First(); if (!KeyFormatter.BufferEqual(Pkcs1KeyFormatter.RsaEncryptionObjectIdentifier, oid.Content)) { throw new ArgumentException("Unexpected algorithm."); } if (elements[1].Class != Asn.BerClass.Universal || elements[1].PC != Asn.BerPC.Primitive || elements[1].Tag != Asn.BerTag.BitString || elements[1].Content[0] != 0) { throw new ArgumentException("Unexpected format."); } byte[] rsaPublicKey = TrimLeadingZero(elements[1].Content); return(KeyFormatter.PublicKeyFilter(KeyFormatter.Pkcs1.Read(rsaPublicKey))); }
/// <summary> /// Writes the core. /// </summary> /// <param name="stream">The stream.</param> /// <param name="value">The value.</param> protected override void WriteCore(Stream stream, RSAParameters value) { Requires.NotNull(stream, "stream"); var sequence = new MemoryStream(); if (KeyFormatter.HasPrivateKey(value)) { // Only include the version element if this is a private key. sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, new byte[1])); } sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, this.prependLeadingZeroOnCertainElements ? PrependLeadingZero(value.Modulus) : value.Modulus)); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, value.Exponent)); if (KeyFormatter.HasPrivateKey(value)) { sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, value.D)); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, this.prependLeadingZeroOnCertainElements ? PrependLeadingZero(value.P) : value.P)); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, this.prependLeadingZeroOnCertainElements ? PrependLeadingZero(value.Q) : value.Q)); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, this.prependLeadingZeroOnCertainElements ? PrependLeadingZero(value.DP) : value.DP)); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, value.DQ)); sequence.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Primitive, Asn.BerTag.Integer, this.prependLeadingZeroOnCertainElements ? PrependLeadingZero(value.InverseQ) : value.InverseQ)); } stream.WriteAsn1Element(new Asn.DataElement(Asn.BerClass.Universal, Asn.BerPC.Constructed, Asn.BerTag.Sequence, sequence.ToArray())); }
/// <summary> /// Reads a key from the specified stream. /// </summary> /// <param name="stream">The stream.</param> /// <returns> /// The RSA Parameters of the key. /// </returns> protected override RSAParameters ReadCore(Stream stream) { var parameters = default(RSAParameters); using var reader = new BinaryReader(stream); bool hasPrivateKey; byte keyBlobHeader = reader.ReadByte(); switch (keyBlobHeader) { case PrivateKeyBlobHeader: hasPrivateKey = true; break; case PublicKeyBlobHeader: hasPrivateKey = false; break; default: throw KeyFormatter.FailFormat(); } byte currentBlobVersion = reader.ReadByte(); KeyFormatter.VerifyFormat(currentBlobVersion == CurrentBlobVersion); short reserved = reader.ReadInt16(); KeyFormatter.VerifyFormat(reserved == 0); int keySpec = reader.ReadInt32(); KeyFormatter.VerifyFormat(keySpec == KeySpecKeyExchange); string magicHeader = Encoding.UTF8.GetString(reader.ReadBytes(4), 0, 4); KeyFormatter.VerifyFormat(hasPrivateKey ? (magicHeader == PrivateKeyMagicHeader) : (magicHeader == PublicKeyMagicHeader)); int bitlen = reader.ReadInt32(); int bytelen = bitlen / 8; parameters.Exponent = ReadReversed(reader, 4); parameters.Modulus = ReadReversed(reader, bytelen); if (hasPrivateKey) { parameters.P = ReadReversed(reader, bytelen / 2); parameters.Q = ReadReversed(reader, bytelen / 2); parameters.DP = ReadReversed(reader, bytelen / 2); parameters.DQ = ReadReversed(reader, bytelen / 2); parameters.InverseQ = ReadReversed(reader, bytelen / 2); parameters.D = ReadReversed(reader, bytelen); } return(parameters); }
/// <summary> /// Reads a key from the specified stream. /// </summary> /// <param name="stream">The stream.</param> /// <returns> /// The RSA Parameters of the key. /// </returns> protected override RSAParameters ReadCore(Stream stream) { var universalConstructedSequence = stream.ReadAsn1Elements().Single(); var sequence = Asn.ReadAsn1Elements(universalConstructedSequence.Content).ToList(); KeyFormatter.VerifyFormat(sequence[0].Content.Length == 1 && sequence[0].Content[0] == 0x00, Strings.UnrecognizedVersion); Asn.DataElement oid = Asn.ReadAsn1Elements(sequence[1].Content).First(); KeyFormatter.VerifyFormat(X509SubjectPublicKeyInfoFormatter.BufferEqual(oid.Content, Pkcs1KeyFormatter.RsaEncryptionObjectIdentifier), Strings.UnrecognizedObjectIdentifier); return(KeyFormatter.Pkcs1.Read(sequence[2].Content)); }
/// <summary> /// Throws an exception if the specified RSAParameters cannot be /// serialized in the CAPI format. /// </summary> /// <param name="parameters">The RSA parameters.</param> internal static void VerifyCapiCompatibleParameters(RSAParameters parameters) { try { KeyFormatter.VerifyFormat(IsCapiCompatible(parameters), "Private key parameters have lengths that are not supported by CAPI."); } catch (FormatException ex) { throw new NotSupportedException(ex.Message, ex); } }
/// <summary>Writes a key to the specified stream.</summary> /// <param name="stream">The stream.</param> /// <param name="parameters">The RSA parameters of the key.</param> protected override void WriteCore(Stream stream, RSAParameters parameters) { if (!IsCapiCompatible(parameters)) { // Try to get the RSA parameters to conform to CAPI's requirements. parameters = NegotiateSizes(parameters); } VerifyCapiCompatibleParameters(parameters); var writer = new BinaryWriter(stream); int bytelen = parameters.Modulus[0] == 0 // if high-order byte is zero, it's for sign bit; don't count in bit-size calculation ? parameters.Modulus.Length - 1 : parameters.Modulus.Length; int bitlen = 8 * bytelen; writer.Write(KeyFormatter.HasPrivateKey(parameters) ? PrivateKeyBlobHeader : PublicKeyBlobHeader); writer.Write(CurrentBlobVersion); writer.Write((short)0); // reserved writer.Write(KeySpecKeyExchange); writer.Write(Encoding.UTF8.GetBytes(KeyFormatter.HasPrivateKey(parameters) ? PrivateKeyMagicHeader : PublicKeyMagicHeader)); writer.Write(bitlen); // Ensure that the exponent occupies 4 bytes in the serialized stream, // even if in the parameters structure it does not. // We cannot use BitConverter.ToInt32 to help us do this because // its behavior varies based on the endianness of the platform, // yet RSAParameters is defined to always be Big Endian, and the // key blob format is defined to always be Little Endian, so we have to be careful. byte[] exponentPadding = new byte[4 - parameters.Exponent.Length]; WriteReversed(writer, parameters.Exponent); writer.Write(exponentPadding); // bytelen drops the sign byte if it is present (which is good) WriteReversed(writer, parameters.Modulus, bytelen); if (KeyFormatter.HasPrivateKey(parameters)) { WriteReversed(writer, parameters.P, bytelen / 2); WriteReversed(writer, parameters.Q, bytelen / 2); WriteReversed(writer, parameters.DP, bytelen / 2); WriteReversed(writer, parameters.DQ, bytelen / 2); WriteReversed(writer, parameters.InverseQ, bytelen / 2); WriteReversed(writer, parameters.D, bytelen); } writer.Flush(); writer.Dispose(); }
/// <summary> /// Reads a key from the specified stream. /// </summary> /// <param name="stream">The stream.</param> /// <returns> /// The RSA Parameters of the key. /// </returns> protected override RSAParameters ReadCore(Stream stream) { var keyBlobElement = Asn.ReadAsn1Elements(stream).First(); KeyFormatter.VerifyFormat( keyBlobElement.Class == Asn.BerClass.Universal && keyBlobElement.PC == Asn.BerPC.Constructed && keyBlobElement.Tag == Asn.BerTag.Sequence); stream = new MemoryStream(keyBlobElement.Content); var sequence = Asn.ReadAsn1Elements(stream).ToList(); switch (sequence.Count) { case 2: return(new RSAParameters { Modulus = sequence[0].Content, Exponent = sequence[1].Content, }); case 9: KeyFormatter.VerifyFormat(sequence[0].Content.Length == 1 && sequence[0].Content[0] == 0, "Unsupported version."); return(new RSAParameters { Modulus = sequence[1].Content, Exponent = sequence[2].Content, D = sequence[3].Content, P = sequence[4].Content, Q = sequence[5].Content, DP = sequence[6].Content, DQ = sequence[7].Content, InverseQ = sequence[8].Content, }); default: throw KeyFormatter.FailFormat(); } }
/// <summary> /// Determines whether the specified RSA parameters /// can be represented in the CAPI format. /// </summary> /// <param name="parameters">The parameters.</param> /// <returns><c>true</c> if CAPI is compatible with these parameters; <c>false</c> otherwise.</returns> internal static bool IsCapiCompatible(RSAParameters parameters) { // Only private keys have this restriction. if (!KeyFormatter.HasPrivateKey(parameters)) { return(true); } int halfModulusLength = (parameters.Modulus.Length + 1) / 2; // These are the same assertions that Windows crypto lib itself // follows when it returns 'bad data'. // CAPI's file format does not include lengths for parameters. // Instead it makes some assumptions about their relative lengths // which make it fundamentally incompatible with some private keys // generated by iOS. return (halfModulusLength == parameters.P.Length && halfModulusLength == parameters.Q.Length && halfModulusLength == parameters.DP.Length && halfModulusLength == parameters.DQ.Length && halfModulusLength == parameters.InverseQ.Length && parameters.Modulus.Length == parameters.D.Length); }