diff options
52 files changed, 4616 insertions, 843 deletions
diff --git a/crypto/License.html b/crypto/License.html index 1c5c7b0ec..cd92d1b0e 100644 --- a/crypto/License.html +++ b/crypto/License.html @@ -9,7 +9,7 @@ <h2>The Bouncy Castle Cryptographic C#® API</h2> <h3>License:</h3> The Bouncy Castle License<br> -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. +Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)<br> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index 81f74e656..fdd5c152b 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -4344,6 +4344,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\BasicTlsPskIdentity.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\ByteQueue.cs" SubType = "Code" BuildAction = "Compile" @@ -4434,6 +4439,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\DatagramTransport.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\DefaultTlsAgreementCredentials.cs" SubType = "Code" BuildAction = "Compile" @@ -4479,6 +4489,56 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\DtlsClientProtocol.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsEpoch.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsHandshakeRetransmit.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsProtocol.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsReassembler.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsRecordLayer.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsReliableHandshake.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsReplayWindow.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsServerProtocol.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File + RelPath = "src\crypto\tls\DtlsTransport.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\ECBasisType.cs" SubType = "Code" BuildAction = "Compile" @@ -4594,6 +4654,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\PskTlsServer.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\RecordStream.cs" SubType = "Code" BuildAction = "Compile" @@ -4849,6 +4914,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\TlsPskIdentityManager.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\TlsRsaKeyExchange.cs" SubType = "Code" BuildAction = "Compile" diff --git a/crypto/src/AssemblyInfo.cs b/crypto/src/AssemblyInfo.cs index 7dd625878..4a813bc5a 100644 --- a/crypto/src/AssemblyInfo.cs +++ b/crypto/src/AssemblyInfo.cs @@ -14,7 +14,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("The Legion of the Bouncy Castle Inc.")] [assembly: AssemblyProduct("Bouncy Castle for .NET")] -[assembly: AssemblyCopyright("Copyright (C) 2000-2014")] +[assembly: AssemblyCopyright("Copyright (C) 2000-2015")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs b/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs index b8aba7ee9..bc48c3fa2 100644 --- a/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs +++ b/crypto/src/asn1/microsoft/MicrosoftObjectIdentifiers.cs @@ -2,17 +2,18 @@ using System; namespace Org.BouncyCastle.Asn1.Microsoft { - public abstract class MicrosoftObjectIdentifiers - { - // - // Microsoft - // iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) Microsoft(311) - // - public static readonly DerObjectIdentifier Microsoft = new DerObjectIdentifier("1.3.6.1.4.1.311"); - public static readonly DerObjectIdentifier MicrosoftCertTemplateV1 = new DerObjectIdentifier(Microsoft + ".20.2"); - public static readonly DerObjectIdentifier MicrosoftCAVersion = new DerObjectIdentifier(Microsoft + ".21.1"); - public static readonly DerObjectIdentifier MicrosoftPrevCACertHash = new DerObjectIdentifier(Microsoft + ".21.2"); - public static readonly DerObjectIdentifier MicrosoftCertTemplateV2 = new DerObjectIdentifier(Microsoft + ".21.7"); - public static readonly DerObjectIdentifier MicrosoftAppPolicies = new DerObjectIdentifier(Microsoft + ".21.10"); - } + public abstract class MicrosoftObjectIdentifiers + { + // + // Microsoft + // iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) Microsoft(311) + // + public static readonly DerObjectIdentifier Microsoft = new DerObjectIdentifier("1.3.6.1.4.1.311"); + public static readonly DerObjectIdentifier MicrosoftCertTemplateV1 = Microsoft.Branch("20.2"); + public static readonly DerObjectIdentifier MicrosoftCAVersion = Microsoft.Branch("21.1"); + public static readonly DerObjectIdentifier MicrosoftPrevCACertHash = Microsoft.Branch("21.2"); + public static readonly DerObjectIdentifier MicrosoftCrlNextPublish = Microsoft.Branch("21.4"); + public static readonly DerObjectIdentifier MicrosoftCertTemplateV2 = Microsoft.Branch("21.7"); + public static readonly DerObjectIdentifier MicrosoftAppPolicies = Microsoft.Branch("21.10"); + } } diff --git a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs index 01004d889..45adce4f7 100644 --- a/crypto/src/asn1/misc/MiscObjectIdentifiers.cs +++ b/crypto/src/asn1/misc/MiscObjectIdentifiers.cs @@ -1,5 +1,3 @@ -using Org.BouncyCastle.Asn1; - namespace Org.BouncyCastle.Asn1.Misc { public abstract class MiscObjectIdentifiers @@ -9,40 +7,47 @@ namespace Org.BouncyCastle.Asn1.Misc // iso/itu(2) joint-assign(16) us(840) uscompany(1) Netscape(113730) cert-extensions(1) } // public static readonly DerObjectIdentifier Netscape = new DerObjectIdentifier("2.16.840.1.113730.1"); - public static readonly DerObjectIdentifier NetscapeCertType = new DerObjectIdentifier(Netscape + ".1"); - public static readonly DerObjectIdentifier NetscapeBaseUrl = new DerObjectIdentifier(Netscape + ".2"); - public static readonly DerObjectIdentifier NetscapeRevocationUrl = new DerObjectIdentifier(Netscape + ".3"); - public static readonly DerObjectIdentifier NetscapeCARevocationUrl = new DerObjectIdentifier(Netscape + ".4"); - public static readonly DerObjectIdentifier NetscapeRenewalUrl = new DerObjectIdentifier(Netscape + ".7"); - public static readonly DerObjectIdentifier NetscapeCAPolicyUrl = new DerObjectIdentifier(Netscape + ".8"); - public static readonly DerObjectIdentifier NetscapeSslServerName = new DerObjectIdentifier(Netscape + ".12"); - public static readonly DerObjectIdentifier NetscapeCertComment = new DerObjectIdentifier(Netscape + ".13"); + public static readonly DerObjectIdentifier NetscapeCertType = Netscape.Branch("1"); + public static readonly DerObjectIdentifier NetscapeBaseUrl = Netscape.Branch("2"); + public static readonly DerObjectIdentifier NetscapeRevocationUrl = Netscape.Branch("3"); + public static readonly DerObjectIdentifier NetscapeCARevocationUrl = Netscape.Branch("4"); + public static readonly DerObjectIdentifier NetscapeRenewalUrl = Netscape.Branch("7"); + public static readonly DerObjectIdentifier NetscapeCAPolicyUrl = Netscape.Branch("8"); + public static readonly DerObjectIdentifier NetscapeSslServerName = Netscape.Branch("12"); + public static readonly DerObjectIdentifier NetscapeCertComment = Netscape.Branch("13"); + // // Verisign // iso/itu(2) joint-assign(16) us(840) uscompany(1) verisign(113733) cert-extensions(1) } // - internal const string Verisign = "2.16.840.1.113733.1"; + public static readonly DerObjectIdentifier Verisign = new DerObjectIdentifier("2.16.840.1.113733.1"); - // + // // CZAG - country, zip, age, and gender // - public static readonly DerObjectIdentifier VerisignCzagExtension = new DerObjectIdentifier(Verisign + ".6.3"); + public static readonly DerObjectIdentifier VerisignCzagExtension = Verisign.Branch("6.3"); - // D&B D-U-N-S number - public static readonly DerObjectIdentifier VerisignDnbDunsNumber = new DerObjectIdentifier(Verisign + ".6.15"); + public static readonly DerObjectIdentifier VerisignPrivate_6_9 = Verisign.Branch("6.9"); + public static readonly DerObjectIdentifier VerisignOnSiteJurisdictionHash = Verisign.Branch("6.11"); + public static readonly DerObjectIdentifier VerisignBitString_6_13 = Verisign.Branch("6.13"); - // - // Novell - // iso/itu(2) country(16) us(840) organization(1) novell(113719) - // - public static readonly string Novell = "2.16.840.1.113719"; - public static readonly DerObjectIdentifier NovellSecurityAttribs = new DerObjectIdentifier(Novell + ".1.9.4.1"); + // D&B D-U-N-S number + public static readonly DerObjectIdentifier VerisignDnbDunsNumber = Verisign.Branch("6.15"); - // - // Entrust - // iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7) - // - public static readonly string Entrust = "1.2.840.113533.7"; - public static readonly DerObjectIdentifier EntrustVersionExtension = new DerObjectIdentifier(Entrust + ".65.0"); - } + public static readonly DerObjectIdentifier VerisignIssStrongCrypto = Verisign.Branch("8.1"); + + // + // Novell + // iso/itu(2) country(16) us(840) organization(1) novell(113719) + // + public static readonly string Novell = "2.16.840.1.113719"; + public static readonly DerObjectIdentifier NovellSecurityAttribs = new DerObjectIdentifier(Novell + ".1.9.4.1"); + + // + // Entrust + // iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7) + // + public static readonly string Entrust = "1.2.840.113533.7"; + public static readonly DerObjectIdentifier EntrustVersionExtension = new DerObjectIdentifier(Entrust + ".65.0"); + } } diff --git a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs index 85ae548eb..4a6704c14 100644 --- a/crypto/src/bcpg/PublicKeyAlgorithmTags.cs +++ b/crypto/src/bcpg/PublicKeyAlgorithmTags.cs @@ -1,6 +1,6 @@ namespace Org.BouncyCastle.Bcpg { - /// <remarks>Public Key Algorithm tag numbers.</remarks> + /// <remarks>Public Key Algorithm tag numbers.</remarks> public enum PublicKeyAlgorithmTag { RsaGeneral = 1, // RSA (Encrypt or Sign) @@ -9,20 +9,21 @@ namespace Org.BouncyCastle.Bcpg ElGamalEncrypt = 16, // Elgamal (Encrypt-Only), see [ELGAMAL] Dsa = 17, // DSA (Digital Signature Standard) EC = 18, // Reserved for Elliptic Curve + ECDH = 18, // Reserved for Elliptic Curve (actual algorithm name) ECDsa = 19, // Reserved for ECDSA ElGamalGeneral = 20, // Elgamal (Encrypt or Sign) DiffieHellman = 21, // Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME) - Experimental_1 = 100, - Experimental_2 = 101, - Experimental_3 = 102, - Experimental_4 = 103, - Experimental_5 = 104, - Experimental_6 = 105, - Experimental_7 = 106, - Experimental_8 = 107, - Experimental_9 = 108, - Experimental_10 = 109, - Experimental_11 = 110, - } + Experimental_1 = 100, + Experimental_2 = 101, + Experimental_3 = 102, + Experimental_4 = 103, + Experimental_5 = 104, + Experimental_6 = 105, + Experimental_7 = 106, + Experimental_8 = 107, + Experimental_9 = 108, + Experimental_10 = 109, + Experimental_11 = 110, + } } diff --git a/crypto/src/cms/SignerInformationStore.cs b/crypto/src/cms/SignerInformationStore.cs index bd613843d..27940865d 100644 --- a/crypto/src/cms/SignerInformationStore.cs +++ b/crypto/src/cms/SignerInformationStore.cs @@ -8,10 +8,31 @@ namespace Org.BouncyCastle.Cms { public class SignerInformationStore { - private readonly IList all; //ArrayList[SignerInformation] - private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[SignerID, ArrayList[SignerInformation]] + private readonly IList all; //ArrayList[SignerInformation] + private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[SignerID, ArrayList[SignerInformation]] - public SignerInformationStore( + /** + * Create a store containing a single SignerInformation object. + * + * @param signerInfo the signer information to contain. + */ + public SignerInformationStore( + SignerInformation signerInfo) + { + this.all = Platform.CreateArrayList(1); + this.all.Add(signerInfo); + + SignerID sid = signerInfo.SignerID; + + table[sid] = all; + } + + /** + * Create a store containing a collection of SignerInformation objects. + * + * @param signerInfos a collection signer information objects to contain. + */ + public SignerInformationStore( ICollection signerInfos) { foreach (SignerInformation signer in signerInfos) @@ -19,12 +40,12 @@ namespace Org.BouncyCastle.Cms SignerID sid = signer.SignerID; IList list = (IList)table[sid]; - if (list == null) - { - table[sid] = list = Platform.CreateArrayList(1); - } + if (list == null) + { + table[sid] = list = Platform.CreateArrayList(1); + } - list.Add(signer); + list.Add(signer); } this.all = Platform.CreateArrayList(signerInfos); @@ -40,24 +61,24 @@ namespace Org.BouncyCastle.Cms public SignerInformation GetFirstSigner( SignerID selector) { - IList list = (IList) table[selector]; + IList list = (IList) table[selector]; - return list == null ? null : (SignerInformation) list[0]; + return list == null ? null : (SignerInformation) list[0]; } - /// <summary>The number of signers in the collection.</summary> - public int Count + /// <summary>The number of signers in the collection.</summary> + public int Count { - get { return all.Count; } + get { return all.Count; } } - /// <returns>An ICollection of all signers in the collection</returns> + /// <returns>An ICollection of all signers in the collection</returns> public ICollection GetSigners() { return Platform.CreateArrayList(all); } - /** + /** * Return possible empty collection with signers matching the passed in SignerID * * @param selector a signer id to select against. @@ -66,7 +87,7 @@ namespace Org.BouncyCastle.Cms public ICollection GetSigners( SignerID selector) { - IList list = (IList) table[selector]; + IList list = (IList) table[selector]; return list == null ? Platform.CreateArrayList() : Platform.CreateArrayList(list); } diff --git a/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs b/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs index 98049e1db..5660a1f84 100644 --- a/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs +++ b/crypto/src/crypto/modes/gcm/BasicGcmExponentiator.cs @@ -4,37 +4,37 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Modes.Gcm { - public class BasicGcmExponentiator - : IGcmExponentiator - { - private byte[] x; + public class BasicGcmExponentiator + : IGcmExponentiator + { + private uint[] x; - public void Init(byte[] x) - { - this.x = Arrays.Clone(x); - } + public void Init(byte[] x) + { + this.x = GcmUtilities.AsUints(x); + } - public void ExponentiateX(long pow, byte[] output) - { - // Initial value is little-endian 1 - byte[] y = GcmUtilities.OneAsBytes(); + public void ExponentiateX(long pow, byte[] output) + { + // Initial value is little-endian 1 + uint[] y = GcmUtilities.OneAsUints(); - if (pow > 0) - { - byte[] powX = Arrays.Clone(x); - do - { - if ((pow & 1L) != 0) - { - GcmUtilities.Multiply(y, powX); - } - GcmUtilities.Multiply(powX, powX); - pow >>= 1; - } - while (pow > 0); - } + if (pow > 0) + { + uint[] powX = Arrays.Clone(x); + do + { + if ((pow & 1L) != 0) + { + GcmUtilities.Multiply(y, powX); + } + GcmUtilities.Multiply(powX, powX); + pow >>= 1; + } + while (pow > 0); + } - Array.Copy(y, 0, output, 0, 16); - } - } + GcmUtilities.AsBytes(y, output); + } + } } diff --git a/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs b/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs index 85e3ac9b1..eb89383fb 100644 --- a/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs +++ b/crypto/src/crypto/modes/gcm/BasicGcmMultiplier.cs @@ -1,22 +1,22 @@ using System; -using Org.BouncyCastle.Utilities; - namespace Org.BouncyCastle.Crypto.Modes.Gcm { - public class BasicGcmMultiplier - : IGcmMultiplier - { - private byte[] H; + public class BasicGcmMultiplier + : IGcmMultiplier + { + private uint[] H; - public void Init(byte[] H) - { - this.H = Arrays.Clone(H); - } + public void Init(byte[] H) + { + this.H = GcmUtilities.AsUints(H); + } public void MultiplyH(byte[] x) - { - GcmUtilities.Multiply(x, H); - } - } + { + uint[] t = GcmUtilities.AsUints(x); + GcmUtilities.Multiply(t, H); + GcmUtilities.AsBytes(t, x); + } + } } diff --git a/crypto/src/crypto/modes/gcm/GcmUtilities.cs b/crypto/src/crypto/modes/gcm/GcmUtilities.cs index 71e63c8fd..0f241035f 100644 --- a/crypto/src/crypto/modes/gcm/GcmUtilities.cs +++ b/crypto/src/crypto/modes/gcm/GcmUtilities.cs @@ -7,6 +7,31 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm { internal abstract class GcmUtilities { + private const uint E1 = 0xe1000000; + private const ulong E1L = (ulong)E1 << 32; + + private static uint[] GenerateLookup() + { + uint[] lookup = new uint[256]; + + for (int c = 0; c < 256; ++c) + { + uint v = 0; + for (int i = 7; i >= 0; --i) + { + if ((c & (1 << i)) != 0) + { + v ^= (E1 >> (7 - i)); + } + } + lookup[c] = v; + } + + return lookup; + } + + private static readonly uint[] LOOKUP = GenerateLookup(); + internal static byte[] OneAsBytes() { byte[] tmp = new byte[16]; @@ -21,6 +46,16 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm return tmp; } + internal static byte[] AsBytes(uint[] x) + { + return Pack.UInt32_To_BE(x); + } + + internal static void AsBytes(uint[] x, byte[] z) + { + Pack.UInt32_To_BE(x, z, 0); + } + internal static uint[] AsUints(byte[] bs) { uint[] output = new uint[4]; @@ -33,56 +68,55 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm Pack.BE_To_UInt32(bs, 0, output); } - internal static void Multiply(byte[] block, byte[] val) + internal static void Multiply(byte[] x, byte[] y) { - byte[] tmp = Arrays.Clone(block); - byte[] c = new byte[16]; + uint[] t1 = GcmUtilities.AsUints(x); + uint[] t2 = GcmUtilities.AsUints(y); + GcmUtilities.Multiply(t1, t2); + GcmUtilities.AsBytes(t1, x); + } - for (int i = 0; i < 16; ++i) + internal static void Multiply(uint[] x, uint[] y) + { + uint r00 = x[0], r01 = x[1], r02 = x[2], r03 = x[3]; + uint r10 = 0, r11 = 0, r12 = 0, r13 = 0; + + for (int i = 0; i < 4; ++i) { - byte bits = val[i]; - for (int j = 7; j >= 0; --j) + int bits = (int)y[i]; + for (int j = 0; j < 32; ++j) { - if ((bits & (1 << j)) != 0) - { - Xor(c, tmp); - } - - bool lsb = (tmp[15] & 1) != 0; - ShiftRight(tmp); - if (lsb) - { - // R = new byte[]{ 0xe1, ... }; - //GCMUtilities.Xor(tmp, R); - tmp[0] ^= (byte)0xe1; - } + uint m1 = (uint)(bits >> 31); bits <<= 1; + r10 ^= (r00 & m1); + r11 ^= (r01 & m1); + r12 ^= (r02 & m1); + r13 ^= (r03 & m1); + + uint m2 = (uint)((int)(r03 << 31) >> 8); + r03 = (r03 >> 1) | (r02 << 63); + r02 = (r02 >> 1) | (r01 << 63); + r01 = (r01 >> 1) | (r00 << 63); + r00 = (r00 >> 1) ^ (m2 & E1); } } - Array.Copy(c, 0, block, 0, 16); + x[0] = r10; + x[1] = r11; + x[2] = r12; + x[3] = r13; } // P is the value with only bit i=1 set internal static void MultiplyP(uint[] x) { - bool lsb = (x[3] & 1) != 0; - ShiftRight(x); - if (lsb) - { - // R = new uint[]{ 0xe1000000, 0, 0, 0 }; - //Xor(v, R); - x[0] ^= 0xe1000000; - } + uint m = (uint)((int)ShiftRight(x) >> 8); + x[0] ^= (m & E1); } - internal static void MultiplyP(uint[] x, uint[] output) + internal static void MultiplyP(uint[] x, uint[] z) { - bool lsb = (x[3] & 1) != 0; - ShiftRight(x, output); - if (lsb) - { - output[0] ^= 0xe1000000; - } + uint m = (uint)((int)ShiftRight(x, z) >> 8); + z[0] ^= (m & E1); } internal static void MultiplyP8(uint[] x) @@ -92,146 +126,128 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm // MultiplyP(x); // } - uint lsw = x[3]; - ShiftRightN(x, 8); - for (int i = 7; i >= 0; --i) - { - if ((lsw & (1 << i)) != 0) - { - x[0] ^= (0xe1000000 >> (7 - i)); - } - } - } - - internal static void MultiplyP8(uint[] x, uint[] output) - { - uint lsw = x[3]; - ShiftRightN(x, 8, output); - for (int i = 7; i >= 0; --i) - { - if ((lsw & (1 << i)) != 0) - { - output[0] ^= (0xe1000000 >> (7 - i)); - } - } + uint c = ShiftRightN(x, 8); + x[0] ^= LOOKUP[c >> 24]; } - internal static void ShiftRight(byte[] block) + internal static void MultiplyP8(uint[] x, uint[] y) { - int i = 0; - byte bit = 0; - for (; ; ) - { - byte b = block[i]; - block[i] = (byte)((b >> 1) | bit); - if (++i == 16) break; - bit = (byte)(b << 7); - } + uint c = ShiftRightN(x, 8, y); + y[0] ^= LOOKUP[c >> 24]; } - static void ShiftRight(byte[] block, byte[] output) + internal static uint ShiftRight(uint[] x) { - int i = 0; - byte bit = 0; - for (;;) - { - byte b = block[i]; - output[i] = (byte)((b >> 1) | bit); - if (++i == 16) break; - bit = (byte)(b << 7); - } + uint b = x[0]; + x[0] = b >> 1; + uint c = b << 31; + b = x[1]; + x[1] = (b >> 1) | c; + c = b << 31; + b = x[2]; + x[2] = (b >> 1) | c; + c = b << 31; + b = x[3]; + x[3] = (b >> 1) | c; + return b << 31; } - internal static void ShiftRight(uint[] block) + internal static uint ShiftRight(uint[] x, uint[] z) { - int i = 0; - uint bit = 0; - for (; ; ) - { - uint b = block[i]; - block[i] = (b >> 1) | bit; - if (++i == 4) break; - bit = b << 31; - } + uint b = x[0]; + z[0] = b >> 1; + uint c = b << 31; + b = x[1]; + z[1] = (b >> 1) | c; + c = b << 31; + b = x[2]; + z[2] = (b >> 1) | c; + c = b << 31; + b = x[3]; + z[3] = (b >> 1) | c; + return b << 31; } - internal static void ShiftRight(uint[] block, uint[] output) + internal static uint ShiftRightN(uint[] x, int n) { - int i = 0; - uint bit = 0; - for (; ; ) - { - uint b = block[i]; - output[i] = (b >> 1) | bit; - if (++i == 4) break; - bit = b << 31; - } + uint b = x[0]; int nInv = 32 - n; + x[0] = b >> n; + uint c = b << nInv; + b = x[1]; + x[1] = (b >> n) | c; + c = b << nInv; + b = x[2]; + x[2] = (b >> n) | c; + c = b << nInv; + b = x[3]; + x[3] = (b >> n) | c; + return b << nInv; } - internal static void ShiftRightN(uint[] block, int n) + internal static uint ShiftRightN(uint[] x, int n, uint[] z) { - int i = 0; - uint bit = 0; - for (; ; ) - { - uint b = block[i]; - block[i] = (b >> n) | bit; - if (++i == 4) break; - bit = b << (32 - n); - } + uint b = x[0]; int nInv = 32 - n; + z[0] = b >> n; + uint c = b << nInv; + b = x[1]; + z[1] = (b >> n) | c; + c = b << nInv; + b = x[2]; + z[2] = (b >> n) | c; + c = b << nInv; + b = x[3]; + z[3] = (b >> n) | c; + return b << nInv; } - internal static void ShiftRightN(uint[] block, int n, uint[] output) + internal static void Xor(byte[] x, byte[] y) { int i = 0; - uint bit = 0; - for (; ; ) - { - uint b = block[i]; - output[i] = (b >> n) | bit; - if (++i == 4) break; - bit = b << (32 - n); - } - } - - internal static void Xor(byte[] block, byte[] val) - { - for (int i = 15; i >= 0; --i) + do { - block[i] ^= val[i]; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; + x[i] ^= y[i]; ++i; } + while (i < 16); } - internal static void Xor(byte[] block, byte[] val, int off, int len) + internal static void Xor(byte[] x, byte[] y, int yOff, int yLen) { - while (--len >= 0) + while (--yLen >= 0) { - block[len] ^= val[off + len]; + x[yLen] ^= y[yOff + yLen]; } } - internal static void Xor(byte[] block, byte[] val, byte[] output) + internal static void Xor(byte[] x, byte[] y, byte[] z) { - for (int i = 15; i >= 0; --i) + int i = 0; + do { - output[i] = (byte)(block[i] ^ val[i]); + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; + z[i] = (byte)(x[i] ^ y[i]); ++i; } + while (i < 16); } - internal static void Xor(uint[] block, uint[] val) + internal static void Xor(uint[] x, uint[] y) { - for (int i = 3; i >= 0; --i) - { - block[i] ^= val[i]; - } + x[0] ^= y[0]; + x[1] ^= y[1]; + x[2] ^= y[2]; + x[3] ^= y[3]; } - internal static void Xor(uint[] block, uint[] val, uint[] output) + internal static void Xor(uint[] x, uint[] y, uint[] z) { - for (int i = 3; i >= 0; --i) - { - output[i] = block[i] ^ val[i]; - } + z[0] = x[0] ^ y[0]; + z[1] = x[1] ^ y[1]; + z[2] = x[2] ^ y[2]; + z[3] = x[3] ^ y[3]; } } } diff --git a/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs b/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs index 44933bba7..e649d6770 100644 --- a/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs +++ b/crypto/src/crypto/modes/gcm/Tables1kGcmExponentiator.cs @@ -5,48 +5,47 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Modes.Gcm { - public class Tables1kGcmExponentiator - : IGcmExponentiator - { + public class Tables1kGcmExponentiator + : IGcmExponentiator + { // A lookup table of the power-of-two powers of 'x' // - lookupPowX2[i] = x^(2^i) private IList lookupPowX2; public void Init(byte[] x) - { - if (lookupPowX2 != null && Arrays.AreEqual(x, (byte[])lookupPowX2[0])) - { + { + uint[] y = GcmUtilities.AsUints(x); + if (lookupPowX2 != null && Arrays.AreEqual(y, (uint[])lookupPowX2[0])) return; - } lookupPowX2 = Platform.CreateArrayList(8); - lookupPowX2.Add(Arrays.Clone(x)); - } + lookupPowX2.Add(y); + } - public void ExponentiateX(long pow, byte[] output) - { - byte[] y = GcmUtilities.OneAsBytes(); + public void ExponentiateX(long pow, byte[] output) + { + uint[] y = GcmUtilities.OneAsUints(); int bit = 0; while (pow > 0) { if ((pow & 1L) != 0) { EnsureAvailable(bit); - GcmUtilities.Multiply(y, (byte[])lookupPowX2[bit]); + GcmUtilities.Multiply(y, (uint[])lookupPowX2[bit]); } ++bit; pow >>= 1; } - Array.Copy(y, 0, output, 0, 16); - } + GcmUtilities.AsBytes(y, output); + } private void EnsureAvailable(int bit) { int count = lookupPowX2.Count; if (count <= bit) { - byte[] tmp = (byte[])lookupPowX2[count - 1]; + uint[] tmp = (uint[])lookupPowX2[count - 1]; do { tmp = Arrays.Clone(tmp); diff --git a/crypto/src/crypto/paddings/Pkcs7Padding.cs b/crypto/src/crypto/paddings/Pkcs7Padding.cs index f3166fd96..11585647a 100644 --- a/crypto/src/crypto/paddings/Pkcs7Padding.cs +++ b/crypto/src/crypto/paddings/Pkcs7Padding.cs @@ -9,7 +9,7 @@ namespace Org.BouncyCastle.Crypto.Paddings * A padder that adds Pkcs7/Pkcs5 padding to a block. */ public class Pkcs7Padding - : IBlockCipherPadding + : IBlockCipherPadding { /** * Initialise the padder. @@ -17,7 +17,7 @@ namespace Org.BouncyCastle.Crypto.Paddings * @param random - a SecureRandom if available. */ public void Init( - SecureRandom random) + SecureRandom random) { // nothing to do. } @@ -32,7 +32,7 @@ namespace Org.BouncyCastle.Crypto.Paddings get { return "PKCS7"; } } - /** + /** * add the pad bytes to the passed in block, returning the * number of bytes added. */ @@ -55,21 +55,18 @@ namespace Org.BouncyCastle.Crypto.Paddings * return the number of pad bytes present in the block. */ public int PadCount( - byte[] input) + byte[] input) { - int count = (int) input[input.Length - 1]; + byte countAsByte = input[input.Length - 1]; + int count = countAsByte; - if (count < 1 || count > input.Length) - { + if (count < 1 || count > input.Length) throw new InvalidCipherTextException("pad block corrupted"); - } - for (int i = 1; i <= count; i++) + for (int i = 2; i <= count; i++) { - if (input[input.Length - i] != count) - { + if (input[input.Length - i] != countAsByte) throw new InvalidCipherTextException("pad block corrupted"); - } } return count; diff --git a/crypto/src/crypto/tls/AbstractTlsClient.cs b/crypto/src/crypto/tls/AbstractTlsClient.cs index 9484afa7d..771bc004b 100644 --- a/crypto/src/crypto/tls/AbstractTlsClient.cs +++ b/crypto/src/crypto/tls/AbstractTlsClient.cs @@ -30,6 +30,32 @@ namespace Org.BouncyCastle.Crypto.Tls this.mCipherFactory = cipherFactory; } + protected virtual bool AllowUnexpectedServerExtension(int extensionType, byte[] extensionData) + { + switch (extensionType) + { + case ExtensionType.elliptic_curves: + /* + * Exception added based on field reports that some servers do send this, although the + * Supported Elliptic Curves Extension is clearly intended to be client-only. If + * present, we still require that it is a valid EllipticCurveList. + */ + TlsEccUtilities.ReadSupportedEllipticCurvesExtension(extensionData); + return true; + default: + return false; + } + } + + protected virtual void CheckForUnexpectedServerExtension(IDictionary serverExtensions, int extensionType) + { + byte[] extensionData = TlsUtilities.GetExtensionData(serverExtensions, extensionType); + if (extensionData != null && !AllowUnexpectedServerExtension(extensionType, extensionData)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + public virtual void Init(TlsClientContext context) { this.mContext = context; @@ -67,6 +93,16 @@ namespace Org.BouncyCastle.Crypto.Tls get { return ProtocolVersion.TLSv12; } } + public virtual bool IsFallback + { + /* + * draft-ietf-tls-downgrade-scsv-00 4. [..] is meant for use by clients that repeat a + * connection attempt with a downgraded protocol in order to avoid interoperability problems + * with legacy servers. + */ + get { return false; } + } + public virtual IDictionary GetClientExtensions() { IDictionary clientExtensions = null; @@ -177,16 +213,18 @@ namespace Org.BouncyCastle.Crypto.Tls /* * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension. */ - if (serverExtensions.Contains(ExtensionType.signature_algorithms)) - throw new TlsFatalAlert(AlertDescription.illegal_parameter); + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.signature_algorithms); - int[] namedCurves = TlsEccUtilities.GetSupportedEllipticCurvesExtension(serverExtensions); - if (namedCurves != null) - throw new TlsFatalAlert(AlertDescription.illegal_parameter); + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.elliptic_curves); - this.mServerECPointFormats = TlsEccUtilities.GetSupportedPointFormatsExtension(serverExtensions); - if (this.mServerECPointFormats != null && !TlsEccUtilities.IsEccCipherSuite(this.mSelectedCipherSuite)) - throw new TlsFatalAlert(AlertDescription.illegal_parameter); + if (TlsEccUtilities.IsEccCipherSuite(this.mSelectedCipherSuite)) + { + this.mServerECPointFormats = TlsEccUtilities.GetSupportedPointFormatsExtension(serverExtensions); + } + else + { + CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.ec_point_formats); + } } } diff --git a/crypto/src/crypto/tls/AbstractTlsServer.cs b/crypto/src/crypto/tls/AbstractTlsServer.cs index c2c6fd57c..b0a5f0d52 100644 --- a/crypto/src/crypto/tls/AbstractTlsServer.cs +++ b/crypto/src/crypto/tls/AbstractTlsServer.cs @@ -110,6 +110,18 @@ namespace Org.BouncyCastle.Crypto.Tls this.mClientVersion = clientVersion; } + public virtual void NotifyFallback(bool isFallback) + { + /* + * draft-ietf-tls-downgrade-scsv-00 3. If TLS_FALLBACK_SCSV appears in + * ClientHello.cipher_suites and the highest protocol version supported by the server is + * higher than the version indicated in ClientHello.client_version, the server MUST respond + * with an inappropriate_fallback alert. + */ + if (isFallback && MaximumVersion.IsLaterVersionOf(mClientVersion)) + throw new TlsFatalAlert(AlertDescription.inappropriate_fallback); + } + public virtual void NotifyOfferedCipherSuites(int[] offeredCipherSuites) { this.mOfferedCipherSuites = offeredCipherSuites; diff --git a/crypto/src/crypto/tls/AlertDescription.cs b/crypto/src/crypto/tls/AlertDescription.cs index 5b6e88bf7..49de60cea 100644 --- a/crypto/src/crypto/tls/AlertDescription.cs +++ b/crypto/src/crypto/tls/AlertDescription.cs @@ -214,6 +214,17 @@ namespace Org.BouncyCastle.Crypto.Tls */ public const byte unknown_psk_identity = 115; + /* + * draft-ietf-tls-downgrade-scsv-00 + */ + + /** + * If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest protocol version + * supported by the server is higher than the version indicated in ClientHello.client_version, + * the server MUST respond with an inappropriate_fallback alert. + */ + public const byte inappropriate_fallback = 86; + public static string GetName(byte alertDescription) { switch (alertDescription) @@ -278,6 +289,8 @@ namespace Org.BouncyCastle.Crypto.Tls return "bad_certificate_hash_value"; case unknown_psk_identity: return "unknown_psk_identity"; + case inappropriate_fallback: + return "inappropriate_fallback"; default: return "UNKNOWN"; } diff --git a/crypto/src/crypto/tls/BasicTlsPskIdentity.cs b/crypto/src/crypto/tls/BasicTlsPskIdentity.cs new file mode 100644 index 000000000..db5954422 --- /dev/null +++ b/crypto/src/crypto/tls/BasicTlsPskIdentity.cs @@ -0,0 +1,43 @@ +using System; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class BasicTlsPskIdentity + : TlsPskIdentity + { + protected byte[] mIdentity; + protected byte[] mPsk; + + public BasicTlsPskIdentity(byte[] identity, byte[] psk) + { + this.mIdentity = Arrays.Clone(identity); + this.mPsk = Arrays.Clone(psk); + } + + public BasicTlsPskIdentity(string identity, byte[] psk) + { + this.mIdentity = Strings.ToUtf8ByteArray(identity); + this.mPsk = Arrays.Clone(psk); + } + + public virtual void SkipIdentityHint() + { + } + + public virtual void NotifyIdentityHint(byte[] psk_identity_hint) + { + } + + public virtual byte[] GetPskIdentity() + { + return mIdentity; + } + + public virtual byte[] GetPsk() + { + return mPsk; + } + } +} diff --git a/crypto/src/crypto/tls/CipherSuite.cs b/crypto/src/crypto/tls/CipherSuite.cs index f034ab802..5ec36aee8 100644 --- a/crypto/src/crypto/tls/CipherSuite.cs +++ b/crypto/src/crypto/tls/CipherSuite.cs @@ -323,6 +323,14 @@ namespace Org.BouncyCastle.Crypto.Tls public const int TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB; /* + * RFC 7251 + */ + public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xC0AC; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xC0AD; + public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xC0AE; + public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xC0AF; + + /* * draft-agl-tls-chacha20poly1305-04 */ public const int TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC13; @@ -348,5 +356,22 @@ namespace Org.BouncyCastle.Crypto.Tls public const int TLS_DHE_PSK_WITH_SALSA20_SHA1 = 0xE41D; public const int TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xE41E; public const int TLS_DHE_RSA_WITH_SALSA20_SHA1 = 0xE41F; + + /* + * draft-ietf-tls-downgrade-scsv-00 + */ + public const int TLS_FALLBACK_SCSV = 0x5600; + + public static bool IsScsv(int cipherSuite) + { + switch (cipherSuite) + { + case TLS_EMPTY_RENEGOTIATION_INFO_SCSV: + case TLS_FALLBACK_SCSV: + return true; + default: + return false; + } + } } } diff --git a/crypto/src/crypto/tls/DatagramTransport.cs b/crypto/src/crypto/tls/DatagramTransport.cs new file mode 100644 index 000000000..524a8b181 --- /dev/null +++ b/crypto/src/crypto/tls/DatagramTransport.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface DatagramTransport + { + /// <exception cref="IOException"/> + int GetReceiveLimit(); + + /// <exception cref="IOException"/> + int GetSendLimit(); + + /// <exception cref="IOException"/> + int Receive(byte[] buf, int off, int len, int waitMillis); + + /// <exception cref="IOException"/> + void Send(byte[] buf, int off, int len); + + /// <exception cref="IOException"/> + void Close(); + } +} diff --git a/crypto/src/crypto/tls/DefaultTlsClient.cs b/crypto/src/crypto/tls/DefaultTlsClient.cs index a2a04a33c..ec98413b7 100644 --- a/crypto/src/crypto/tls/DefaultTlsClient.cs +++ b/crypto/src/crypto/tls/DefaultTlsClient.cs @@ -145,9 +145,13 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: @@ -258,10 +262,12 @@ namespace Org.BouncyCastle.Crypto.Tls return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256); case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: case CipherSuite.TLS_RSA_WITH_AES_128_CCM: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null); case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null); @@ -301,10 +307,12 @@ namespace Org.BouncyCastle.Crypto.Tls return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384); case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: case CipherSuite.TLS_RSA_WITH_AES_256_CCM: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null); case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null); diff --git a/crypto/src/crypto/tls/DefaultTlsServer.cs b/crypto/src/crypto/tls/DefaultTlsServer.cs index 017ed0d85..75f6d8d88 100644 --- a/crypto/src/crypto/tls/DefaultTlsServer.cs +++ b/crypto/src/crypto/tls/DefaultTlsServer.cs @@ -236,9 +236,13 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: @@ -347,10 +351,12 @@ namespace Org.BouncyCastle.Crypto.Tls return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256); case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: case CipherSuite.TLS_RSA_WITH_AES_128_CCM: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null); case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null); @@ -390,10 +396,12 @@ namespace Org.BouncyCastle.Crypto.Tls return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384); case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: case CipherSuite.TLS_RSA_WITH_AES_256_CCM: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null); case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null); diff --git a/crypto/src/crypto/tls/DtlsClientProtocol.cs b/crypto/src/crypto/tls/DtlsClientProtocol.cs new file mode 100644 index 000000000..2aa4df692 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsClientProtocol.cs @@ -0,0 +1,844 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DtlsClientProtocol + : DtlsProtocol + { + public DtlsClientProtocol(SecureRandom secureRandom) + : base(secureRandom) + { + } + + public virtual DtlsTransport Connect(TlsClient client, DatagramTransport transport) + { + if (client == null) + throw new ArgumentNullException("client"); + if (transport == null) + throw new ArgumentNullException("transport"); + + SecurityParameters securityParameters = new SecurityParameters(); + securityParameters.entity = ConnectionEnd.client; + + ClientHandshakeState state = new ClientHandshakeState(); + state.client = client; + state.clientContext = new TlsClientContextImpl(mSecureRandom, securityParameters); + + securityParameters.clientRandom = TlsProtocol.CreateRandomBlock(client.ShouldUseGmtUnixTime(), + state.clientContext.NonceRandomGenerator); + + client.Init(state.clientContext); + + DtlsRecordLayer recordLayer = new DtlsRecordLayer(transport, state.clientContext, client, ContentType.handshake); + + TlsSession sessionToResume = state.client.GetSessionToResume(); + if (sessionToResume != null) + { + SessionParameters sessionParameters = sessionToResume.ExportSessionParameters(); + if (sessionParameters != null) + { + state.tlsSession = sessionToResume; + state.sessionParameters = sessionParameters; + } + } + + try + { + return ClientHandshake(state, recordLayer); + } + catch (TlsFatalAlert fatalAlert) + { + recordLayer.Fail(fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + recordLayer.Fail(AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + recordLayer.Fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + internal virtual DtlsTransport ClientHandshake(ClientHandshakeState state, DtlsRecordLayer recordLayer) + { + SecurityParameters securityParameters = state.clientContext.SecurityParameters; + DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.clientContext, recordLayer); + + byte[] clientHelloBody = GenerateClientHello(state, state.client); + handshake.SendMessage(HandshakeType.client_hello, clientHelloBody); + + DtlsReliableHandshake.Message serverMessage = handshake.ReceiveMessage(); + + while (serverMessage.Type == HandshakeType.hello_verify_request) + { + ProtocolVersion recordLayerVersion = recordLayer.ResetDiscoveredPeerVersion(); + ProtocolVersion client_version = state.clientContext.ClientVersion; + + /* + * RFC 6347 4.2.1 DTLS 1.2 server implementations SHOULD use DTLS version 1.0 regardless of + * the version of TLS that is expected to be negotiated. DTLS 1.2 and 1.0 clients MUST use + * the version solely to indicate packet formatting (which is the same in both DTLS 1.2 and + * 1.0) and not as part of version negotiation. + */ + if (!recordLayerVersion.IsEqualOrEarlierVersionOf(client_version)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + byte[] cookie = ProcessHelloVerifyRequest(state, serverMessage.Body); + byte[] patched = PatchClientHelloWithCookie(clientHelloBody, cookie); + + handshake.ResetHandshakeMessagesDigest(); + handshake.SendMessage(HandshakeType.client_hello, patched); + + serverMessage = handshake.ReceiveMessage(); + } + + if (serverMessage.Type == HandshakeType.server_hello) + { + ReportServerVersion(state, recordLayer.DiscoveredPeerVersion); + + ProcessServerHello(state, serverMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + if (state.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + state.maxFragmentLength); + recordLayer.SetPlaintextLimit(plainTextLimit); + } + + securityParameters.cipherSuite = state.selectedCipherSuite; + securityParameters.compressionAlgorithm = (byte)state.selectedCompressionMethod; + securityParameters.prfAlgorithm = TlsProtocol.GetPrfAlgorithm(state.clientContext, state.selectedCipherSuite); + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has + * a verify_data_length equal to 12. This includes all existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + handshake.NotifyHelloComplete(); + + bool resumedSession = state.selectedSessionID.Length > 0 && state.tlsSession != null + && Arrays.AreEqual(state.selectedSessionID, state.tlsSession.SessionID); + + if (resumedSession) + { + if (securityParameters.CipherSuite != state.sessionParameters.CipherSuite + || securityParameters.CompressionAlgorithm != state.sessionParameters.CompressionAlgorithm) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + IDictionary sessionServerExtensions = state.sessionParameters.ReadServerExtensions(); + + securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(sessionServerExtensions); + + securityParameters.masterSecret = Arrays.Clone(state.sessionParameters.MasterSecret); + recordLayer.InitPendingEpoch(state.client.GetCipher()); + + // NOTE: Calculated exclusive of the actual Finished message from the server + byte[] resExpectedServerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.server_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), resExpectedServerVerifyData); + + // NOTE: Calculated exclusive of the Finished message itself + byte[] resClientVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.client_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + handshake.SendMessage(HandshakeType.finished, resClientVerifyData); + + handshake.Finish(); + + state.clientContext.SetResumableSession(state.tlsSession); + + state.client.NotifyHandshakeComplete(); + + return new DtlsTransport(recordLayer); + } + + InvalidateSession(state); + + if (state.selectedSessionID.Length > 0) + { + state.tlsSession = new TlsSessionImpl(state.selectedSessionID, null); + } + + serverMessage = handshake.ReceiveMessage(); + + if (serverMessage.Type == HandshakeType.supplemental_data) + { + ProcessServerSupplementalData(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + state.client.ProcessServerSupplementalData(null); + } + + state.keyExchange = state.client.GetKeyExchange(); + state.keyExchange.Init(state.clientContext); + + Certificate serverCertificate = null; + + if (serverMessage.Type == HandshakeType.certificate) + { + serverCertificate = ProcessServerCertificate(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, Certificate is optional + state.keyExchange.SkipServerCredentials(); + } + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.IsEmpty) + { + state.allowCertificateStatus = false; + } + + if (serverMessage.Type == HandshakeType.certificate_status) + { + ProcessCertificateStatus(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, CertificateStatus is optional + } + + if (serverMessage.Type == HandshakeType.server_key_exchange) + { + ProcessServerKeyExchange(state, serverMessage.Body); + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, ServerKeyExchange is optional + state.keyExchange.SkipServerKeyExchange(); + } + + if (serverMessage.Type == HandshakeType.certificate_request) + { + ProcessCertificateRequest(state, serverMessage.Body); + + /* + * TODO Give the client a chance to immediately select the CertificateVerify hash + * algorithm here to avoid tracking the other hash algorithms unnecessarily? + */ + TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash, + state.certificateRequest.SupportedSignatureAlgorithms); + + serverMessage = handshake.ReceiveMessage(); + } + else + { + // Okay, CertificateRequest is optional + } + + if (serverMessage.Type == HandshakeType.server_hello_done) + { + if (serverMessage.Body.Length != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + handshake.HandshakeHash.SealHashAlgorithms(); + + IList clientSupplementalData = state.client.GetClientSupplementalData(); + if (clientSupplementalData != null) + { + byte[] supplementalDataBody = GenerateSupplementalData(clientSupplementalData); + handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody); + } + + if (state.certificateRequest != null) + { + state.clientCredentials = state.authentication.GetClientCredentials(state.certificateRequest); + + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a certificate + * message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + Certificate clientCertificate = null; + if (state.clientCredentials != null) + { + clientCertificate = state.clientCredentials.Certificate; + } + if (clientCertificate == null) + { + clientCertificate = Certificate.EmptyChain; + } + + byte[] certificateBody = GenerateCertificate(clientCertificate); + handshake.SendMessage(HandshakeType.certificate, certificateBody); + } + + if (state.clientCredentials != null) + { + state.keyExchange.ProcessClientCredentials(state.clientCredentials); + } + else + { + state.keyExchange.SkipClientCredentials(); + } + + byte[] clientKeyExchangeBody = GenerateClientKeyExchange(state); + handshake.SendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody); + + TlsHandshakeHash prepareFinishHash = handshake.PrepareToFinish(); + securityParameters.sessionHash = TlsProtocol.GetCurrentPrfHash(state.clientContext, prepareFinishHash, null); + + TlsProtocol.EstablishMasterSecret(state.clientContext, state.keyExchange); + recordLayer.InitPendingEpoch(state.client.GetCipher()); + + if (state.clientCredentials != null && state.clientCredentials is TlsSignerCredentials) + { + TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials; + + /* + * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm; + byte[] hash; + + if (TlsUtilities.IsTlsV12(state.clientContext)) + { + signatureAndHashAlgorithm = signerCredentials.SignatureAndHashAlgorithm; + if (signatureAndHashAlgorithm == null) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + hash = prepareFinishHash.GetFinalHash(signatureAndHashAlgorithm.Hash); + } + else + { + signatureAndHashAlgorithm = null; + hash = securityParameters.SessionHash; + } + + byte[] signature = signerCredentials.GenerateCertificateSignature(hash); + DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature); + byte[] certificateVerifyBody = GenerateCertificateVerify(state, certificateVerify); + handshake.SendMessage(HandshakeType.certificate_verify, certificateVerifyBody); + } + + // NOTE: Calculated exclusive of the Finished message itself + byte[] clientVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.client_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + handshake.SendMessage(HandshakeType.finished, clientVerifyData); + + if (state.expectSessionTicket) + { + serverMessage = handshake.ReceiveMessage(); + if (serverMessage.Type == HandshakeType.session_ticket) + { + ProcessNewSessionTicket(state, serverMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + // NOTE: Calculated exclusive of the actual Finished message from the server + byte[] expectedServerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext, ExporterLabel.server_finished, + TlsProtocol.GetCurrentPrfHash(state.clientContext, handshake.HandshakeHash, null)); + ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), expectedServerVerifyData); + + handshake.Finish(); + + if (state.tlsSession != null) + { + state.sessionParameters = new SessionParameters.Builder() + .SetCipherSuite(securityParameters.cipherSuite) + .SetCompressionAlgorithm(securityParameters.compressionAlgorithm) + .SetMasterSecret(securityParameters.masterSecret) + .SetPeerCertificate(serverCertificate) + .SetPskIdentity(securityParameters.pskIdentity) + .Build(); + + state.tlsSession = TlsUtilities.ImportSession(state.tlsSession.SessionID, state.sessionParameters); + + state.clientContext.SetResumableSession(state.tlsSession); + } + + state.client.NotifyHandshakeComplete(); + + return new DtlsTransport(recordLayer); + } + + protected virtual byte[] GenerateCertificateVerify(ClientHandshakeState state, DigitallySigned certificateVerify) + { + MemoryStream buf = new MemoryStream(); + certificateVerify.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateClientHello(ClientHandshakeState state, TlsClient client) + { + MemoryStream buf = new MemoryStream(); + + ProtocolVersion client_version = client.ClientVersion; + if (!client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.internal_error); + + TlsClientContextImpl context = state.clientContext; + + context.SetClientVersion(client_version); + TlsUtilities.WriteVersion(client_version, buf); + + SecurityParameters securityParameters = context.SecurityParameters; + buf.Write(securityParameters.ClientRandom, 0, securityParameters.ClientRandom.Length); + + // Session ID + byte[] session_id = TlsUtilities.EmptyBytes; + if (state.tlsSession != null) + { + session_id = state.tlsSession.SessionID; + if (session_id == null || session_id.Length > 32) + { + session_id = TlsUtilities.EmptyBytes; + } + } + TlsUtilities.WriteOpaque8(session_id, buf); + + // Cookie + TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, buf); + + bool fallback = client.IsFallback; + + /* + * Cipher suites + */ + state.offeredCipherSuites = client.GetCipherSuites(); + + // Integer -> byte[] + state.clientExtensions = client.GetClientExtensions(); + + securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(state.clientExtensions); + + // Cipher Suites (and SCSV) + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions, ExtensionType.renegotiation_info); + bool noRenegExt = (null == renegExtData); + + bool noRenegSCSV = !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + + if (noRenegExt && noRenegSCSV) + { + // TODO Consider whether to default to a client extension instead + state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + } + + /* + * draft-ietf-tls-downgrade-scsv-00 4. If a client sends a ClientHello.client_version + * containing a lower value than the latest (highest-valued) version supported by the + * client, it SHOULD include the TLS_FALLBACK_SCSV cipher suite value in + * ClientHello.cipher_suites. + */ + if (fallback && !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)) + { + state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV); + } + + TlsUtilities.WriteUint16ArrayWithUint16Length(state.offeredCipherSuites, buf); + } + + // TODO Add support for compression + // Compression methods + // state.offeredCompressionMethods = client.getCompressionMethods(); + state.offeredCompressionMethods = new byte[]{ CompressionMethod.cls_null }; + + TlsUtilities.WriteUint8ArrayWithUint8Length(state.offeredCompressionMethods, buf); + + // Extensions + if (state.clientExtensions != null) + { + TlsProtocol.WriteExtensions(buf, state.clientExtensions); + } + + return buf.ToArray(); + } + + protected virtual byte[] GenerateClientKeyExchange(ClientHandshakeState state) + { + MemoryStream buf = new MemoryStream(); + state.keyExchange.GenerateClientKeyExchange(buf); + return buf.ToArray(); + } + + protected virtual void InvalidateSession(ClientHandshakeState state) + { + if (state.sessionParameters != null) + { + state.sessionParameters.Clear(); + state.sessionParameters = null; + } + + if (state.tlsSession != null) + { + state.tlsSession.Invalidate(); + state.tlsSession = null; + } + } + + protected virtual void ProcessCertificateRequest(ClientHandshakeState state, byte[] body) + { + if (state.authentication == null) + { + /* + * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to + * request client identification. + */ + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + MemoryStream buf = new MemoryStream(body, false); + + state.certificateRequest = CertificateRequest.Parse(state.clientContext, buf); + + TlsProtocol.AssertEmpty(buf); + + state.keyExchange.ValidateCertificateRequest(state.certificateRequest); + } + + protected virtual void ProcessCertificateStatus(ClientHandshakeState state, byte[] body) + { + if (!state.allowCertificateStatus) + { + /* + * RFC 3546 3.6. If a server returns a "CertificateStatus" message, then the + * server MUST have included an extension of type "status_request" with empty + * "extension_data" in the extended server hello.. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + MemoryStream buf = new MemoryStream(body, false); + + state.certificateStatus = CertificateStatus.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + // TODO[RFC 3546] Figure out how to provide this to the client/authentication. + } + + protected virtual byte[] ProcessHelloVerifyRequest(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + ProtocolVersion server_version = TlsUtilities.ReadVersion(buf); + byte[] cookie = TlsUtilities.ReadOpaque8(buf); + + TlsProtocol.AssertEmpty(buf); + + // TODO Seems this behaviour is not yet in line with OpenSSL for DTLS 1.2 + // reportServerVersion(state, server_version); + if (!server_version.IsEqualOrEarlierVersionOf(state.clientContext.ClientVersion)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + /* + * RFC 6347 This specification increases the cookie size limit to 255 bytes for greater + * future flexibility. The limit remains 32 for previous versions of DTLS. + */ + if (!ProtocolVersion.DTLSv12.IsEqualOrEarlierVersionOf(server_version) && cookie.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return cookie; + } + + protected virtual void ProcessNewSessionTicket(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + state.client.NotifyNewSessionTicket(newSessionTicket); + } + + protected virtual Certificate ProcessServerCertificate(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + Certificate serverCertificate = Certificate.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + state.keyExchange.ProcessServerCertificate(serverCertificate); + state.authentication = state.client.GetAuthentication(); + state.authentication.NotifyServerCertificate(serverCertificate); + + return serverCertificate; + } + + protected virtual void ProcessServerHello(ClientHandshakeState state, byte[] body) + { + SecurityParameters securityParameters = state.clientContext.SecurityParameters; + + MemoryStream buf = new MemoryStream(body, false); + + ProtocolVersion server_version = TlsUtilities.ReadVersion(buf); + ReportServerVersion(state, server_version); + + securityParameters.serverRandom = TlsUtilities.ReadFully(32, buf); + + state.selectedSessionID = TlsUtilities.ReadOpaque8(buf); + if (state.selectedSessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + state.client.NotifySessionID(state.selectedSessionID); + + state.selectedCipherSuite = TlsUtilities.ReadUint16(buf); + if (!Arrays.Contains(state.offeredCipherSuites, state.selectedCipherSuite) + || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || CipherSuite.IsScsv(state.selectedCipherSuite) + || !TlsUtilities.IsValidCipherSuiteForVersion(state.selectedCipherSuite, server_version)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + ValidateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.illegal_parameter); + + state.client.NotifySelectedCipherSuite(state.selectedCipherSuite); + + state.selectedCompressionMethod = TlsUtilities.ReadUint8(buf); + if (!Arrays.Contains(state.offeredCompressionMethods, (byte)state.selectedCompressionMethod)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + state.client.NotifySelectedCompressionMethod((byte)state.selectedCompressionMethod); + + /* + * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server + * hello message when the client has requested extended functionality via the extended + * client hello message specified in Section 2.1. ... Note that the extended server hello + * message is only sent in response to an extended client hello message. This prevents the + * possibility that the extended server hello message could "break" existing TLS 1.0 + * clients. + */ + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + + // Integer -> byte[] + IDictionary serverExtensions = TlsProtocol.ReadExtensions(buf); + + /* + * draft-ietf-tls-session-hash-01 5.2. If a server receives the "extended_master_secret" + * extension, it MUST include the "extended_master_secret" extension in its ServerHello + * message. + */ + bool serverSentExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(serverExtensions); + if (serverSentExtendedMasterSecret != securityParameters.extendedMasterSecret) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + + /* + * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an + * extended client hello message. However, see RFC 5746 exception below. We always include + * the SCSV, so an Extended Server Hello is always allowed. + */ + if (serverExtensions != null) + { + foreach (int extType in serverExtensions.Keys) + { + /* + * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a + * ClientHello containing only the SCSV is an explicit exception to the prohibition + * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is + * only allowed because the client is signaling its willingness to receive the + * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + if (extType == ExtensionType.renegotiation_info) + continue; + + /* + * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the + * same extension type appeared in the corresponding ClientHello. If a client + * receives an extension type in ServerHello that it did not request in the + * associated ClientHello, it MUST abort the handshake with an unsupported_extension + * fatal alert. + */ + if (null == TlsUtilities.GetExtensionData(state.clientExtensions, extType)) + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + + /* + * draft-ietf-tls-session-hash-01 5.2. Implementation note: if the server decides to + * proceed with resumption, the extension does not have any effect. Requiring the + * extension to be included anyway makes the extension negotiation logic easier, + * because it does not depend on whether resumption is accepted or not. + */ + if (extType == ExtensionType.extended_master_secret) + continue; + + /* + * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions[.] + */ + // TODO[sessions] + // if (this.mResumedSession) + // { + // // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats + // // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats + // // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats + //// throw new TlsFatalAlert(AlertDescription.illegal_parameter); + // } + } + + /* + * RFC 5746 3.4. Client Behavior: Initial Handshake + */ + { + /* + * When a ServerHello is received, the client MUST check if it includes the + * "renegotiation_info" extension: + */ + byte[] renegExtData = (byte[])serverExtensions[ExtensionType.renegotiation_info]; + if (renegExtData != null) + { + /* + * If the extension is present, set the secure_renegotiation flag to TRUE. The + * client MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake (by sending a fatal + * handshake_failure alert). + */ + state.secure_renegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + /* + * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client + * and then selects a stream or Authenticated Encryption with Associated Data (AEAD) + * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the + * client. + */ + bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(serverExtensions); + if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(state.selectedCipherSuite)) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + securityParameters.encryptThenMac = serverSentEncryptThenMAC; + + state.maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.clientExtensions, serverExtensions, + AlertDescription.illegal_parameter); + + securityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(serverExtensions); + + state.allowCertificateStatus = TlsUtilities.HasExpectedEmptyExtensionData(serverExtensions, + ExtensionType.status_request, AlertDescription.illegal_parameter); + + state.expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(serverExtensions, + ExtensionType.session_ticket, AlertDescription.illegal_parameter); + } + + state.client.NotifySecureRenegotiation(state.secure_renegotiation); + + if (state.clientExtensions != null) + { + state.client.ProcessServerExtensions(serverExtensions); + } + } + + protected virtual void ProcessServerKeyExchange(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + state.keyExchange.ProcessServerKeyExchange(buf); + + TlsProtocol.AssertEmpty(buf); + } + + protected virtual void ProcessServerSupplementalData(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + IList serverSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf); + state.client.ProcessServerSupplementalData(serverSupplementalData); + } + + protected virtual void ReportServerVersion(ClientHandshakeState state, ProtocolVersion server_version) + { + TlsClientContextImpl clientContext = state.clientContext; + ProtocolVersion currentServerVersion = clientContext.ServerVersion; + if (null == currentServerVersion) + { + clientContext.SetServerVersion(server_version); + state.client.NotifyServerVersion(server_version); + } + else if (!currentServerVersion.Equals(server_version)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + protected static byte[] PatchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie) + { + int sessionIDPos = 34; + int sessionIDLength = TlsUtilities.ReadUint8(clientHelloBody, sessionIDPos); + + int cookieLengthPos = sessionIDPos + 1 + sessionIDLength; + int cookiePos = cookieLengthPos + 1; + + byte[] patched = new byte[clientHelloBody.Length + cookie.Length]; + Array.Copy(clientHelloBody, 0, patched, 0, cookieLengthPos); + TlsUtilities.CheckUint8(cookie.Length); + TlsUtilities.WriteUint8((byte)cookie.Length, patched, cookieLengthPos); + Array.Copy(cookie, 0, patched, cookiePos, cookie.Length); + Array.Copy(clientHelloBody, cookiePos, patched, cookiePos + cookie.Length, clientHelloBody.Length - cookiePos); + + return patched; + } + + protected internal class ClientHandshakeState + { + internal TlsClient client = null; + internal TlsClientContextImpl clientContext = null; + internal TlsSession tlsSession = null; + internal SessionParameters sessionParameters = null; + internal SessionParameters.Builder sessionParametersBuilder = null; + internal int[] offeredCipherSuites = null; + internal byte[] offeredCompressionMethods = null; + internal IDictionary clientExtensions = null; + internal byte[] selectedSessionID = null; + internal int selectedCipherSuite = -1; + internal short selectedCompressionMethod = -1; + internal bool secure_renegotiation = false; + internal short maxFragmentLength = -1; + internal bool allowCertificateStatus = false; + internal bool expectSessionTicket = false; + internal TlsKeyExchange keyExchange = null; + internal TlsAuthentication authentication = null; + internal CertificateStatus certificateStatus = null; + internal CertificateRequest certificateRequest = null; + internal TlsCredentials clientCredentials = null; + } + } +} diff --git a/crypto/src/crypto/tls/DtlsEpoch.cs b/crypto/src/crypto/tls/DtlsEpoch.cs new file mode 100644 index 000000000..91fffa5e1 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsEpoch.cs @@ -0,0 +1,51 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DtlsEpoch + { + private readonly DtlsReplayWindow mReplayWindow = new DtlsReplayWindow(); + + private readonly int mEpoch; + private readonly TlsCipher mCipher; + + private long mSequenceNumber = 0; + + internal DtlsEpoch(int epoch, TlsCipher cipher) + { + if (epoch < 0) + throw new ArgumentException("must be >= 0", "epoch"); + if (cipher == null) + throw new ArgumentNullException("cipher"); + + this.mEpoch = epoch; + this.mCipher = cipher; + } + + internal long AllocateSequenceNumber() + { + // TODO Check for overflow + return mSequenceNumber++; + } + + internal TlsCipher Cipher + { + get { return mCipher; } + } + + internal int Epoch + { + get { return mEpoch; } + } + + internal DtlsReplayWindow ReplayWindow + { + get { return mReplayWindow; } + } + + internal long SequenceNumber + { + get { return mSequenceNumber; } + } + } +} diff --git a/crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs b/crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs new file mode 100644 index 000000000..8bfae78b1 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsHandshakeRetransmit.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + interface DtlsHandshakeRetransmit + { + /// <exception cref="IOException"/> + void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len); + } +} diff --git a/crypto/src/crypto/tls/DtlsProtocol.cs b/crypto/src/crypto/tls/DtlsProtocol.cs new file mode 100644 index 000000000..6d62c5a90 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsProtocol.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class DtlsProtocol + { + protected readonly SecureRandom mSecureRandom; + + protected DtlsProtocol(SecureRandom secureRandom) + { + if (secureRandom == null) + throw new ArgumentNullException("secureRandom"); + + this.mSecureRandom = secureRandom; + } + + /// <exception cref="IOException"/> + protected virtual void ProcessFinished(byte[] body, byte[] expected_verify_data) + { + MemoryStream buf = new MemoryStream(body, false); + + byte[] verify_data = TlsUtilities.ReadFully(expected_verify_data.Length, buf); + + TlsProtocol.AssertEmpty(buf); + + if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data)) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + /// <exception cref="IOException"/> + protected static short EvaluateMaxFragmentLengthExtension(IDictionary clientExtensions, IDictionary serverExtensions, + byte alertDescription) + { + short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions); + if (maxFragmentLength >= 0 && maxFragmentLength != TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions)) + throw new TlsFatalAlert(alertDescription); + return maxFragmentLength; + } + + /// <exception cref="IOException"/> + protected static byte[] GenerateCertificate(Certificate certificate) + { + MemoryStream buf = new MemoryStream(); + certificate.Encode(buf); + return buf.ToArray(); + } + + /// <exception cref="IOException"/> + protected static byte[] GenerateSupplementalData(IList supplementalData) + { + MemoryStream buf = new MemoryStream(); + TlsProtocol.WriteSupplementalData(buf, supplementalData); + return buf.ToArray(); + } + + /// <exception cref="IOException"/> + protected static void ValidateSelectedCipherSuite(int selectedCipherSuite, byte alertDescription) + { + switch (TlsUtilities.GetEncryptionAlgorithm(selectedCipherSuite)) + { + case EncryptionAlgorithm.RC4_40: + case EncryptionAlgorithm.RC4_128: + throw new TlsFatalAlert(alertDescription); + } + } + } +} diff --git a/crypto/src/crypto/tls/DtlsReassembler.cs b/crypto/src/crypto/tls/DtlsReassembler.cs new file mode 100644 index 000000000..11fe609cf --- /dev/null +++ b/crypto/src/crypto/tls/DtlsReassembler.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + class DtlsReassembler + { + private readonly byte mMsgType; + private readonly byte[] mBody; + + private readonly IList mMissing = Platform.CreateArrayList(); + + internal DtlsReassembler(byte msg_type, int length) + { + this.mMsgType = msg_type; + this.mBody = new byte[length]; + this.mMissing.Add(new Range(0, length)); + } + + internal byte MsgType + { + get { return mMsgType; } + } + + internal byte[] GetBodyIfComplete() + { + return mMissing.Count == 0 ? mBody : null; + } + + internal void ContributeFragment(byte msg_type, int length, byte[] buf, int off, int fragment_offset, + int fragment_length) + { + int fragment_end = fragment_offset + fragment_length; + + if (this.mMsgType != msg_type || this.mBody.Length != length || fragment_end > length) + { + return; + } + + if (fragment_length == 0) + { + // NOTE: Empty messages still require an empty fragment to complete it + if (fragment_offset == 0 && mMissing.Count > 0) + { + Range firstRange = (Range)mMissing[0]; + if (firstRange.End == 0) + { + mMissing.RemoveAt(0); + } + } + return; + } + + for (int i = 0; i < mMissing.Count; ++i) + { + Range range = (Range)mMissing[i]; + if (range.Start >= fragment_end) + { + break; + } + if (range.End > fragment_offset) + { + + int copyStart = System.Math.Max(range.Start, fragment_offset); + int copyEnd = System.Math.Min(range.End, fragment_end); + int copyLength = copyEnd - copyStart; + + Array.Copy(buf, off + copyStart - fragment_offset, mBody, copyStart, + copyLength); + + if (copyStart == range.Start) + { + if (copyEnd == range.End) + { + mMissing.RemoveAt(i--); + } + else + { + range.Start = copyEnd; + } + } + else + { + if (copyEnd != range.End) + { + mMissing.Insert(++i, new Range(copyEnd, range.End)); + } + range.End = copyStart; + } + } + } + } + + internal void Reset() + { + this.mMissing.Clear(); + this.mMissing.Add(new Range(0, mBody.Length)); + } + + private class Range + { + private int mStart, mEnd; + + internal Range(int start, int end) + { + this.mStart = start; + this.mEnd = end; + } + + public int Start + { + get { return mStart; } + set { this.mStart = value; } + } + + public int End + { + get { return mEnd; } + set { this.mEnd = value; } + } + } + } +} diff --git a/crypto/src/crypto/tls/DtlsRecordLayer.cs b/crypto/src/crypto/tls/DtlsRecordLayer.cs new file mode 100644 index 000000000..70befd9e4 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsRecordLayer.cs @@ -0,0 +1,507 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DtlsRecordLayer + : DatagramTransport + { + private const int RECORD_HEADER_LENGTH = 13; + private const int MAX_FRAGMENT_LENGTH = 1 << 14; + private const long TCP_MSL = 1000L * 60 * 2; + private const long RETRANSMIT_TIMEOUT = TCP_MSL * 2; + + private readonly DatagramTransport mTransport; + private readonly TlsContext mContext; + private readonly TlsPeer mPeer; + + private readonly ByteQueue mRecordQueue = new ByteQueue(); + + private volatile bool mClosed = false; + private volatile bool mFailed = false; + private volatile ProtocolVersion mDiscoveredPeerVersion = null; + private volatile bool mInHandshake; + private volatile int mPlaintextLimit; + private DtlsEpoch mCurrentEpoch, mPendingEpoch; + private DtlsEpoch mReadEpoch, mWriteEpoch; + + private DtlsHandshakeRetransmit mRetransmit = null; + private DtlsEpoch mRetransmitEpoch = null; + private long mRetransmitExpiry = 0; + + internal DtlsRecordLayer(DatagramTransport transport, TlsContext context, TlsPeer peer, byte contentType) + { + this.mTransport = transport; + this.mContext = context; + this.mPeer = peer; + + this.mInHandshake = true; + + this.mCurrentEpoch = new DtlsEpoch(0, new TlsNullCipher(context)); + this.mPendingEpoch = null; + this.mReadEpoch = mCurrentEpoch; + this.mWriteEpoch = mCurrentEpoch; + + SetPlaintextLimit(MAX_FRAGMENT_LENGTH); + } + + internal virtual void SetPlaintextLimit(int plaintextLimit) + { + this.mPlaintextLimit = plaintextLimit; + } + + internal virtual ProtocolVersion DiscoveredPeerVersion + { + get { return mDiscoveredPeerVersion; } + } + + internal virtual ProtocolVersion ResetDiscoveredPeerVersion() + { + ProtocolVersion result = mDiscoveredPeerVersion; + mDiscoveredPeerVersion = null; + return result; + } + + internal virtual void InitPendingEpoch(TlsCipher pendingCipher) + { + if (mPendingEpoch != null) + throw new InvalidOperationException(); + + /* + * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations + * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment + * lifetime." + */ + + // TODO Check for overflow + this.mPendingEpoch = new DtlsEpoch(mWriteEpoch.Epoch + 1, pendingCipher); + } + + internal virtual void HandshakeSuccessful(DtlsHandshakeRetransmit retransmit) + { + if (mReadEpoch == mCurrentEpoch || mWriteEpoch == mCurrentEpoch) + { + // TODO + throw new InvalidOperationException(); + } + + if (retransmit != null) + { + this.mRetransmit = retransmit; + this.mRetransmitEpoch = mCurrentEpoch; + this.mRetransmitExpiry = DateTimeUtilities.CurrentUnixMs() + RETRANSMIT_TIMEOUT; + } + + this.mInHandshake = false; + this.mCurrentEpoch = mPendingEpoch; + this.mPendingEpoch = null; + } + + internal virtual void ResetWriteEpoch() + { + if (mRetransmitEpoch != null) + { + this.mWriteEpoch = mRetransmitEpoch; + } + else + { + this.mWriteEpoch = mCurrentEpoch; + } + } + + public virtual int GetReceiveLimit() + { + return System.Math.Min(this.mPlaintextLimit, + mReadEpoch.Cipher.GetPlaintextLimit(mTransport.GetReceiveLimit() - RECORD_HEADER_LENGTH)); + } + + public virtual int GetSendLimit() + { + return System.Math.Min(this.mPlaintextLimit, + mWriteEpoch.Cipher.GetPlaintextLimit(mTransport.GetSendLimit() - RECORD_HEADER_LENGTH)); + } + + public virtual int Receive(byte[] buf, int off, int len, int waitMillis) + { + byte[] record = null; + + for (;;) + { + int receiveLimit = System.Math.Min(len, GetReceiveLimit()) + RECORD_HEADER_LENGTH; + if (record == null || record.Length < receiveLimit) + { + record = new byte[receiveLimit]; + } + + try + { + if (mRetransmit != null && DateTimeUtilities.CurrentUnixMs() > mRetransmitExpiry) + { + mRetransmit = null; + mRetransmitEpoch = null; + } + + int received = ReceiveRecord(record, 0, receiveLimit, waitMillis); + if (received < 0) + { + return received; + } + if (received < RECORD_HEADER_LENGTH) + { + continue; + } + int length = TlsUtilities.ReadUint16(record, 11); + if (received != (length + RECORD_HEADER_LENGTH)) + { + continue; + } + + byte type = TlsUtilities.ReadUint8(record, 0); + + // TODO Support user-specified custom protocols? + switch (type) + { + case ContentType.alert: + case ContentType.application_data: + case ContentType.change_cipher_spec: + case ContentType.handshake: + case ContentType.heartbeat: + break; + default: + // TODO Exception? + continue; + } + + int epoch = TlsUtilities.ReadUint16(record, 3); + + DtlsEpoch recordEpoch = null; + if (epoch == mReadEpoch.Epoch) + { + recordEpoch = mReadEpoch; + } + else if (type == ContentType.handshake && mRetransmitEpoch != null + && epoch == mRetransmitEpoch.Epoch) + { + recordEpoch = mRetransmitEpoch; + } + + if (recordEpoch == null) + { + continue; + } + + long seq = TlsUtilities.ReadUint48(record, 5); + if (recordEpoch.ReplayWindow.ShouldDiscard(seq)) + { + continue; + } + + ProtocolVersion version = TlsUtilities.ReadVersion(record, 1); + if (mDiscoveredPeerVersion != null && !mDiscoveredPeerVersion.Equals(version)) + { + continue; + } + + byte[] plaintext = recordEpoch.Cipher.DecodeCiphertext( + GetMacSequenceNumber(recordEpoch.Epoch, seq), type, record, RECORD_HEADER_LENGTH, + received - RECORD_HEADER_LENGTH); + + recordEpoch.ReplayWindow.ReportAuthenticated(seq); + + if (plaintext.Length > this.mPlaintextLimit) + { + continue; + } + + if (mDiscoveredPeerVersion == null) + { + mDiscoveredPeerVersion = version; + } + + switch (type) + { + case ContentType.alert: + { + if (plaintext.Length == 2) + { + byte alertLevel = plaintext[0]; + byte alertDescription = plaintext[1]; + + mPeer.NotifyAlertReceived(alertLevel, alertDescription); + + if (alertLevel == AlertLevel.fatal) + { + Fail(alertDescription); + throw new TlsFatalAlert(alertDescription); + } + + // TODO Can close_notify be a fatal alert? + if (alertDescription == AlertDescription.close_notify) + { + CloseTransport(); + } + } + + continue; + } + case ContentType.application_data: + { + if (mInHandshake) + { + // TODO Consider buffering application data for new epoch that arrives + // out-of-order with the Finished message + continue; + } + break; + } + case ContentType.change_cipher_spec: + { + // Implicitly receive change_cipher_spec and change to pending cipher state + + for (int i = 0; i < plaintext.Length; ++i) + { + byte message = TlsUtilities.ReadUint8(plaintext, i); + if (message != ChangeCipherSpec.change_cipher_spec) + { + continue; + } + + if (mPendingEpoch != null) + { + mReadEpoch = mPendingEpoch; + } + } + + continue; + } + case ContentType.handshake: + { + if (!mInHandshake) + { + if (mRetransmit != null) + { + mRetransmit.ReceivedHandshakeRecord(epoch, plaintext, 0, plaintext.Length); + } + + // TODO Consider support for HelloRequest + continue; + } + break; + } + case ContentType.heartbeat: + { + // TODO[RFC 6520] + continue; + } + } + + /* + * NOTE: If we receive any non-handshake data in the new epoch implies the peer has + * received our final flight. + */ + if (!mInHandshake && mRetransmit != null) + { + this.mRetransmit = null; + this.mRetransmitEpoch = null; + } + + Array.Copy(plaintext, 0, buf, off, plaintext.Length); + return plaintext.Length; + } + catch (IOException e) + { + // NOTE: Assume this is a timeout for the moment + throw e; + } + } + } + + /// <exception cref="IOException"/> + public virtual void Send(byte[] buf, int off, int len) + { + byte contentType = ContentType.application_data; + + if (this.mInHandshake || this.mWriteEpoch == this.mRetransmitEpoch) + { + contentType = ContentType.handshake; + + byte handshakeType = TlsUtilities.ReadUint8(buf, off); + if (handshakeType == HandshakeType.finished) + { + DtlsEpoch nextEpoch = null; + if (this.mInHandshake) + { + nextEpoch = mPendingEpoch; + } + else if (this.mWriteEpoch == this.mRetransmitEpoch) + { + nextEpoch = mCurrentEpoch; + } + + if (nextEpoch == null) + { + // TODO + throw new InvalidOperationException(); + } + + // Implicitly send change_cipher_spec and change to pending cipher state + + // TODO Send change_cipher_spec and finished records in single datagram? + byte[] data = new byte[]{ 1 }; + SendRecord(ContentType.change_cipher_spec, data, 0, data.Length); + + mWriteEpoch = nextEpoch; + } + } + + SendRecord(contentType, buf, off, len); + } + + public virtual void Close() + { + if (!mClosed) + { + if (mInHandshake) + { + Warn(AlertDescription.user_canceled, "User canceled handshake"); + } + CloseTransport(); + } + } + + internal virtual void Fail(byte alertDescription) + { + if (!mClosed) + { + try + { + RaiseAlert(AlertLevel.fatal, alertDescription, null, null); + } + catch (Exception) + { + // Ignore + } + + mFailed = true; + + CloseTransport(); + } + } + + internal virtual void Warn(byte alertDescription, string message) + { + RaiseAlert(AlertLevel.warning, alertDescription, message, null); + } + + private void CloseTransport() + { + if (!mClosed) + { + /* + * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is + * required to send a close_notify alert before closing the write side of the + * connection. The other party MUST respond with a close_notify alert of its own and + * close down the connection immediately, discarding any pending writes. + */ + + try + { + if (!mFailed) + { + Warn(AlertDescription.close_notify, null); + } + mTransport.Close(); + } + catch (Exception) + { + // Ignore + } + + mClosed = true; + } + } + + private void RaiseAlert(byte alertLevel, byte alertDescription, string message, Exception cause) + { + mPeer.NotifyAlertRaised(alertLevel, alertDescription, message, cause); + + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + + SendRecord(ContentType.alert, error, 0, 2); + } + + private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis) + { + if (mRecordQueue.Available > 0) + { + int length = 0; + if (mRecordQueue.Available >= RECORD_HEADER_LENGTH) + { + byte[] lengthBytes = new byte[2]; + mRecordQueue.Read(lengthBytes, 0, 2, 11); + length = TlsUtilities.ReadUint16(lengthBytes, 0); + } + + int received = System.Math.Min(mRecordQueue.Available, RECORD_HEADER_LENGTH + length); + mRecordQueue.RemoveData(buf, off, received, 0); + return received; + } + + { + int received = mTransport.Receive(buf, off, len, waitMillis); + if (received >= RECORD_HEADER_LENGTH) + { + int fragmentLength = TlsUtilities.ReadUint16(buf, off + 11); + int recordLength = RECORD_HEADER_LENGTH + fragmentLength; + if (received > recordLength) + { + mRecordQueue.AddData(buf, off + recordLength, received - recordLength); + received = recordLength; + } + } + return received; + } + } + + private void SendRecord(byte contentType, byte[] buf, int off, int len) + { + if (len > this.mPlaintextLimit) + throw new TlsFatalAlert(AlertDescription.internal_error); + + /* + * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert, + * or ChangeCipherSpec content types. + */ + if (len < 1 && contentType != ContentType.application_data) + throw new TlsFatalAlert(AlertDescription.internal_error); + + int recordEpoch = mWriteEpoch.Epoch; + long recordSequenceNumber = mWriteEpoch.AllocateSequenceNumber(); + + byte[] ciphertext = mWriteEpoch.Cipher.EncodePlaintext( + GetMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len); + + // TODO Check the ciphertext length? + + byte[] record = new byte[ciphertext.Length + RECORD_HEADER_LENGTH]; + TlsUtilities.WriteUint8(contentType, record, 0); + ProtocolVersion version = mDiscoveredPeerVersion != null ? mDiscoveredPeerVersion : mContext.ClientVersion; + TlsUtilities.WriteVersion(version, record, 1); + TlsUtilities.WriteUint16(recordEpoch, record, 3); + TlsUtilities.WriteUint48(recordSequenceNumber, record, 5); + TlsUtilities.WriteUint16(ciphertext.Length, record, 11); + Array.Copy(ciphertext, 0, record, RECORD_HEADER_LENGTH, ciphertext.Length); + + mTransport.Send(record, 0, record.Length); + } + + private static long GetMacSequenceNumber(int epoch, long sequence_number) + { + return ((epoch & 0xFFFFFFFFL) << 48) | sequence_number; + } + } +} diff --git a/crypto/src/crypto/tls/DtlsReliableHandshake.cs b/crypto/src/crypto/tls/DtlsReliableHandshake.cs new file mode 100644 index 000000000..bf9e61d03 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsReliableHandshake.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class DtlsReliableHandshake + { + private const int MAX_RECEIVE_AHEAD = 10; + + private readonly DtlsRecordLayer mRecordLayer; + + private TlsHandshakeHash mHandshakeHash; + + private IDictionary mCurrentInboundFlight = Platform.CreateHashtable(); + private IDictionary mPreviousInboundFlight = null; + private IList mOutboundFlight = Platform.CreateArrayList(); + private bool mSending = true; + + private int mMessageSeq = 0, mNextReceiveSeq = 0; + + internal DtlsReliableHandshake(TlsContext context, DtlsRecordLayer transport) + { + this.mRecordLayer = transport; + this.mHandshakeHash = new DeferredHash(); + this.mHandshakeHash.Init(context); + } + + internal void NotifyHelloComplete() + { + this.mHandshakeHash = mHandshakeHash.NotifyPrfDetermined(); + } + + internal TlsHandshakeHash HandshakeHash + { + get { return mHandshakeHash; } + } + + internal TlsHandshakeHash PrepareToFinish() + { + TlsHandshakeHash result = mHandshakeHash; + this.mHandshakeHash = mHandshakeHash.StopTracking(); + return result; + } + + internal void SendMessage(byte msg_type, byte[] body) + { + TlsUtilities.CheckUint24(body.Length); + + if (!mSending) + { + CheckInboundFlight(); + mSending = true; + mOutboundFlight.Clear(); + } + + Message message = new Message(mMessageSeq++, msg_type, body); + + mOutboundFlight.Add(message); + + WriteMessage(message); + UpdateHandshakeMessagesDigest(message); + } + + internal byte[] ReceiveMessageBody(byte msg_type) + { + Message message = ReceiveMessage(); + if (message.Type != msg_type) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + return message.Body; + } + + internal Message ReceiveMessage() + { + if (mSending) + { + mSending = false; + PrepareInboundFlight(); + } + + // Check if we already have the next message waiting + { + DtlsReassembler next = (DtlsReassembler)mCurrentInboundFlight[mNextReceiveSeq]; + if (next != null) + { + byte[] body = next.GetBodyIfComplete(); + if (body != null) + { + mPreviousInboundFlight = null; + return UpdateHandshakeMessagesDigest(new Message(mNextReceiveSeq++, next.MsgType, body)); + } + } + } + + byte[] buf = null; + + // TODO Check the conditions under which we should reset this + int readTimeoutMillis = 1000; + + for (;;) + { + int receiveLimit = mRecordLayer.GetReceiveLimit(); + if (buf == null || buf.Length < receiveLimit) + { + buf = new byte[receiveLimit]; + } + + // TODO Handle records containing multiple handshake messages + + try + { + for (; ; ) + { + int Received = mRecordLayer.Receive(buf, 0, receiveLimit, readTimeoutMillis); + if (Received < 0) + { + break; + } + if (Received < 12) + { + continue; + } + int fragment_length = TlsUtilities.ReadUint24(buf, 9); + if (Received != (fragment_length + 12)) + { + continue; + } + int seq = TlsUtilities.ReadUint16(buf, 4); + if (seq > (mNextReceiveSeq + MAX_RECEIVE_AHEAD)) + { + continue; + } + byte msg_type = TlsUtilities.ReadUint8(buf, 0); + int length = TlsUtilities.ReadUint24(buf, 1); + int fragment_offset = TlsUtilities.ReadUint24(buf, 6); + if (fragment_offset + fragment_length > length) + { + continue; + } + + if (seq < mNextReceiveSeq) + { + /* + * NOTE: If we Receive the previous flight of incoming messages in full + * again, retransmit our last flight + */ + if (mPreviousInboundFlight != null) + { + DtlsReassembler reassembler = (DtlsReassembler)mPreviousInboundFlight[seq]; + if (reassembler != null) + { + reassembler.ContributeFragment(msg_type, length, buf, 12, fragment_offset, + fragment_length); + + if (CheckAll(mPreviousInboundFlight)) + { + ResendOutboundFlight(); + + /* + * TODO[DTLS] implementations SHOULD back off handshake packet + * size during the retransmit backoff. + */ + readTimeoutMillis = System.Math.Min(readTimeoutMillis * 2, 60000); + + ResetAll(mPreviousInboundFlight); + } + } + } + } + else + { + DtlsReassembler reassembler = (DtlsReassembler)mCurrentInboundFlight[seq]; + if (reassembler == null) + { + reassembler = new DtlsReassembler(msg_type, length); + mCurrentInboundFlight[seq] = reassembler; + } + + reassembler.ContributeFragment(msg_type, length, buf, 12, fragment_offset, fragment_length); + + if (seq == mNextReceiveSeq) + { + byte[] body = reassembler.GetBodyIfComplete(); + if (body != null) + { + mPreviousInboundFlight = null; + return UpdateHandshakeMessagesDigest(new Message(mNextReceiveSeq++, + reassembler.MsgType, body)); + } + } + } + } + } + catch (IOException) + { + // NOTE: Assume this is a timeout for the moment + } + + ResendOutboundFlight(); + + /* + * TODO[DTLS] implementations SHOULD back off handshake packet size during the + * retransmit backoff. + */ + readTimeoutMillis = System.Math.Min(readTimeoutMillis * 2, 60000); + } + } + + internal void Finish() + { + DtlsHandshakeRetransmit retransmit = null; + if (!mSending) + { + CheckInboundFlight(); + } + else if (mCurrentInboundFlight != null) + { + /* + * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP], + * when in the FINISHED state, the node that transmits the last flight (the server in an + * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit + * of the peer's last flight with a retransmit of the last flight. + */ + retransmit = new Retransmit(this); + } + + mRecordLayer.HandshakeSuccessful(retransmit); + } + + internal void ResetHandshakeMessagesDigest() + { + mHandshakeHash.Reset(); + } + + private void HandleRetransmittedHandshakeRecord(int epoch, byte[] buf, int off, int len) + { + /* + * TODO Need to handle the case where the previous inbound flight contains + * messages from two epochs. + */ + if (len < 12) + return; + int fragment_length = TlsUtilities.ReadUint24(buf, off + 9); + if (len != (fragment_length + 12)) + return; + int seq = TlsUtilities.ReadUint16(buf, off + 4); + if (seq >= mNextReceiveSeq) + return; + + byte msg_type = TlsUtilities.ReadUint8(buf, off); + + // TODO This is a hack that only works until we try to support renegotiation + int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0; + if (epoch != expectedEpoch) + return; + + int length = TlsUtilities.ReadUint24(buf, off + 1); + int fragment_offset = TlsUtilities.ReadUint24(buf, off + 6); + if (fragment_offset + fragment_length > length) + return; + + DtlsReassembler reassembler = (DtlsReassembler)mCurrentInboundFlight[seq]; + if (reassembler != null) + { + reassembler.ContributeFragment(msg_type, length, buf, off + 12, fragment_offset, + fragment_length); + if (CheckAll(mCurrentInboundFlight)) + { + ResendOutboundFlight(); + ResetAll(mCurrentInboundFlight); + } + } + } + + /** + * Check that there are no "extra" messages left in the current inbound flight + */ + private void CheckInboundFlight() + { + foreach (int key in mCurrentInboundFlight.Keys) + { + if (key >= mNextReceiveSeq) + { + // TODO Should this be considered an error? + } + } + } + + private void PrepareInboundFlight() + { + ResetAll(mCurrentInboundFlight); + mPreviousInboundFlight = mCurrentInboundFlight; + mCurrentInboundFlight = Platform.CreateHashtable(); + } + + private void ResendOutboundFlight() + { + mRecordLayer.ResetWriteEpoch(); + for (int i = 0; i < mOutboundFlight.Count; ++i) + { + WriteMessage((Message)mOutboundFlight[i]); + } + } + + private Message UpdateHandshakeMessagesDigest(Message message) + { + if (message.Type != HandshakeType.hello_request) + { + byte[] body = message.Body; + byte[] buf = new byte[12]; + TlsUtilities.WriteUint8(message.Type, buf, 0); + TlsUtilities.WriteUint24(body.Length, buf, 1); + TlsUtilities.WriteUint16(message.Seq, buf, 4); + TlsUtilities.WriteUint24(0, buf, 6); + TlsUtilities.WriteUint24(body.Length, buf, 9); + mHandshakeHash.BlockUpdate(buf, 0, buf.Length); + mHandshakeHash.BlockUpdate(body, 0, body.Length); + } + return message; + } + + private void WriteMessage(Message message) + { + int sendLimit = mRecordLayer.GetSendLimit(); + int fragmentLimit = sendLimit - 12; + + // TODO Support a higher minimum fragment size? + if (fragmentLimit < 1) + { + // TODO Should we be throwing an exception here? + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + int length = message.Body.Length; + + // NOTE: Must still send a fragment if body is empty + int fragment_offset = 0; + do + { + int fragment_length = System.Math.Min(length - fragment_offset, fragmentLimit); + WriteHandshakeFragment(message, fragment_offset, fragment_length); + fragment_offset += fragment_length; + } + while (fragment_offset < length); + } + + private void WriteHandshakeFragment(Message message, int fragment_offset, int fragment_length) + { + RecordLayerBuffer fragment = new RecordLayerBuffer(12 + fragment_length); + TlsUtilities.WriteUint8(message.Type, fragment); + TlsUtilities.WriteUint24(message.Body.Length, fragment); + TlsUtilities.WriteUint16(message.Seq, fragment); + TlsUtilities.WriteUint24(fragment_offset, fragment); + TlsUtilities.WriteUint24(fragment_length, fragment); + fragment.Write(message.Body, fragment_offset, fragment_length); + + fragment.SendToRecordLayer(mRecordLayer); + } + + private static bool CheckAll(IDictionary inboundFlight) + { + foreach (DtlsReassembler r in inboundFlight.Values) + { + if (r.GetBodyIfComplete() == null) + { + return false; + } + } + return true; + } + + private static void ResetAll(IDictionary inboundFlight) + { + foreach (DtlsReassembler r in inboundFlight.Values) + { + r.Reset(); + } + } + + internal class Message + { + private readonly int mMessageSeq; + private readonly byte mMsgType; + private readonly byte[] mBody; + + internal Message(int message_seq, byte msg_type, byte[] body) + { + this.mMessageSeq = message_seq; + this.mMsgType = msg_type; + this.mBody = body; + } + + public int Seq + { + get { return mMessageSeq; } + } + + public byte Type + { + get { return mMsgType; } + } + + public byte[] Body + { + get { return mBody; } + } + } + + internal class RecordLayerBuffer + : MemoryStream + { + internal RecordLayerBuffer(int size) + : base(size) + { + } + + internal void SendToRecordLayer(DtlsRecordLayer recordLayer) + { + recordLayer.Send(GetBuffer(), 0, (int)Length); + this.Close(); + } + } + + internal class Retransmit + : DtlsHandshakeRetransmit + { + private readonly DtlsReliableHandshake mOuter; + + internal Retransmit(DtlsReliableHandshake outer) + { + this.mOuter = outer; + } + + public void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len) + { + mOuter.HandleRetransmittedHandshakeRecord(epoch, buf, off, len); + } + } + } +} diff --git a/crypto/src/crypto/tls/DtlsReplayWindow.cs b/crypto/src/crypto/tls/DtlsReplayWindow.cs new file mode 100644 index 000000000..ea18e805e --- /dev/null +++ b/crypto/src/crypto/tls/DtlsReplayWindow.cs @@ -0,0 +1,85 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * RFC 4347 4.1.2.5 Anti-replay + * <p/> + * Support fast rejection of duplicate records by maintaining a sliding receive window + */ + internal class DtlsReplayWindow + { + private const long VALID_SEQ_MASK = 0x0000FFFFFFFFFFFFL; + + private const long WINDOW_SIZE = 64L; + + private long mLatestConfirmedSeq = -1; + private long mBitmap = 0; + + /** + * Check whether a received record with the given sequence number should be rejected as a duplicate. + * + * @param seq the 48-bit DTLSPlainText.sequence_number field of a received record. + * @return true if the record should be discarded without further processing. + */ + internal bool ShouldDiscard(long seq) + { + if ((seq & VALID_SEQ_MASK) != seq) + return true; + + if (seq <= mLatestConfirmedSeq) + { + long diff = mLatestConfirmedSeq - seq; + if (diff >= WINDOW_SIZE) + return true; + if ((mBitmap & (1L << (int)diff)) != 0) + return true; + } + + return false; + } + + /** + * Report that a received record with the given sequence number passed authentication checks. + * + * @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record. + */ + internal void ReportAuthenticated(long seq) + { + if ((seq & VALID_SEQ_MASK) != seq) + throw new ArgumentException("out of range", "seq"); + + if (seq <= mLatestConfirmedSeq) + { + long diff = mLatestConfirmedSeq - seq; + if (diff < WINDOW_SIZE) + { + mBitmap |= (1L << (int)diff); + } + } + else + { + long diff = seq - mLatestConfirmedSeq; + if (diff >= WINDOW_SIZE) + { + mBitmap = 1; + } + else + { + mBitmap <<= (int)diff; + mBitmap |= 1; + } + mLatestConfirmedSeq = seq; + } + } + + /** + * When a new epoch begins, sequence numbers begin again at 0 + */ + internal void Reset() + { + mLatestConfirmedSeq = -1; + mBitmap = 0; + } + } +} diff --git a/crypto/src/crypto/tls/DtlsServerProtocol.cs b/crypto/src/crypto/tls/DtlsServerProtocol.cs new file mode 100644 index 000000000..3335a9f36 --- /dev/null +++ b/crypto/src/crypto/tls/DtlsServerProtocol.cs @@ -0,0 +1,642 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DtlsServerProtocol + : DtlsProtocol + { + protected bool mVerifyRequests = true; + + public DtlsServerProtocol(SecureRandom secureRandom) + : base(secureRandom) + { + } + + public virtual bool VerifyRequests + { + get { return mVerifyRequests; } + set { this.mVerifyRequests = value; } + } + + public virtual DtlsTransport Accept(TlsServer server, DatagramTransport transport) + { + if (server == null) + throw new ArgumentNullException("server"); + if (transport == null) + throw new ArgumentNullException("transport"); + + SecurityParameters securityParameters = new SecurityParameters(); + securityParameters.entity = ConnectionEnd.server; + + ServerHandshakeState state = new ServerHandshakeState(); + state.server = server; + state.serverContext = new TlsServerContextImpl(mSecureRandom, securityParameters); + + securityParameters.serverRandom = TlsProtocol.CreateRandomBlock(server.ShouldUseGmtUnixTime(), + state.serverContext.NonceRandomGenerator); + + server.Init(state.serverContext); + + DtlsRecordLayer recordLayer = new DtlsRecordLayer(transport, state.serverContext, server, ContentType.handshake); + + // TODO Need to handle sending of HelloVerifyRequest without entering a full connection + + try + { + return ServerHandshake(state, recordLayer); + } + catch (TlsFatalAlert fatalAlert) + { + recordLayer.Fail(fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + recordLayer.Fail(AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + recordLayer.Fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + internal virtual DtlsTransport ServerHandshake(ServerHandshakeState state, DtlsRecordLayer recordLayer) + { + SecurityParameters securityParameters = state.serverContext.SecurityParameters; + DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.serverContext, recordLayer); + + DtlsReliableHandshake.Message clientMessage = handshake.ReceiveMessage(); + + { + // NOTE: After receiving a record from the client, we discover the record layer version + ProtocolVersion client_version = recordLayer.DiscoveredPeerVersion; + // TODO Read RFCs for guidance on the expected record layer version number + state.serverContext.SetClientVersion(client_version); + } + + if (clientMessage.Type == HandshakeType.client_hello) + { + ProcessClientHello(state, clientMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + { + byte[] serverHelloBody = GenerateServerHello(state); + + if (state.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + state.maxFragmentLength); + recordLayer.SetPlaintextLimit(plainTextLimit); + } + + securityParameters.cipherSuite = state.selectedCipherSuite; + securityParameters.compressionAlgorithm = (byte)state.selectedCompressionMethod; + securityParameters.prfAlgorithm = TlsProtocol.GetPrfAlgorithm(state.serverContext, + state.selectedCipherSuite); + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length + * has a verify_data_length equal to 12. This includes all existing cipher suites. + */ + securityParameters.verifyDataLength = 12; + + handshake.SendMessage(HandshakeType.server_hello, serverHelloBody); + } + + handshake.NotifyHelloComplete(); + + IList serverSupplementalData = state.server.GetServerSupplementalData(); + if (serverSupplementalData != null) + { + byte[] supplementalDataBody = GenerateSupplementalData(serverSupplementalData); + handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody); + } + + state.keyExchange = state.server.GetKeyExchange(); + state.keyExchange.Init(state.serverContext); + + state.serverCredentials = state.server.GetCredentials(); + + Certificate serverCertificate = null; + + if (state.serverCredentials == null) + { + state.keyExchange.SkipServerCredentials(); + } + else + { + state.keyExchange.ProcessServerCredentials(state.serverCredentials); + + serverCertificate = state.serverCredentials.Certificate; + byte[] certificateBody = GenerateCertificate(serverCertificate); + handshake.SendMessage(HandshakeType.certificate, certificateBody); + } + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.IsEmpty) + { + state.allowCertificateStatus = false; + } + + if (state.allowCertificateStatus) + { + CertificateStatus certificateStatus = state.server.GetCertificateStatus(); + if (certificateStatus != null) + { + byte[] certificateStatusBody = GenerateCertificateStatus(state, certificateStatus); + handshake.SendMessage(HandshakeType.certificate_status, certificateStatusBody); + } + } + + byte[] serverKeyExchange = state.keyExchange.GenerateServerKeyExchange(); + if (serverKeyExchange != null) + { + handshake.SendMessage(HandshakeType.server_key_exchange, serverKeyExchange); + } + + if (state.serverCredentials != null) + { + state.certificateRequest = state.server.GetCertificateRequest(); + if (state.certificateRequest != null) + { + state.keyExchange.ValidateCertificateRequest(state.certificateRequest); + + byte[] certificateRequestBody = GenerateCertificateRequest(state, state.certificateRequest); + handshake.SendMessage(HandshakeType.certificate_request, certificateRequestBody); + + TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash, + state.certificateRequest.SupportedSignatureAlgorithms); + } + } + + handshake.SendMessage(HandshakeType.server_hello_done, TlsUtilities.EmptyBytes); + + handshake.HandshakeHash.SealHashAlgorithms(); + + clientMessage = handshake.ReceiveMessage(); + + if (clientMessage.Type == HandshakeType.supplemental_data) + { + ProcessClientSupplementalData(state, clientMessage.Body); + clientMessage = handshake.ReceiveMessage(); + } + else + { + state.server.ProcessClientSupplementalData(null); + } + + if (state.certificateRequest == null) + { + state.keyExchange.SkipClientCredentials(); + } + else + { + if (clientMessage.Type == HandshakeType.certificate) + { + ProcessClientCertificate(state, clientMessage.Body); + clientMessage = handshake.ReceiveMessage(); + } + else + { + if (TlsUtilities.IsTlsV12(state.serverContext)) + { + /* + * RFC 5246 If no suitable certificate is available, the client MUST send a + * certificate message containing no certificates. + * + * NOTE: In previous RFCs, this was SHOULD instead of MUST. + */ + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + NotifyClientCertificate(state, Certificate.EmptyChain); + } + } + + if (clientMessage.Type == HandshakeType.client_key_exchange) + { + ProcessClientKeyExchange(state, clientMessage.Body); + } + else + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + TlsHandshakeHash prepareFinishHash = handshake.PrepareToFinish(); + securityParameters.sessionHash = TlsProtocol.GetCurrentPrfHash(state.serverContext, prepareFinishHash, null); + + TlsProtocol.EstablishMasterSecret(state.serverContext, state.keyExchange); + recordLayer.InitPendingEpoch(state.server.GetCipher()); + + /* + * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing + * capability (i.e., all certificates except those containing fixed Diffie-Hellman + * parameters). + */ + if (ExpectCertificateVerifyMessage(state)) + { + byte[] certificateVerifyBody = handshake.ReceiveMessageBody(HandshakeType.certificate_verify); + ProcessCertificateVerify(state, certificateVerifyBody, prepareFinishHash); + } + + // NOTE: Calculated exclusive of the actual Finished message from the client + byte[] expectedClientVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext, ExporterLabel.client_finished, + TlsProtocol.GetCurrentPrfHash(state.serverContext, handshake.HandshakeHash, null)); + ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), expectedClientVerifyData); + + if (state.expectSessionTicket) + { + NewSessionTicket newSessionTicket = state.server.GetNewSessionTicket(); + byte[] newSessionTicketBody = GenerateNewSessionTicket(state, newSessionTicket); + handshake.SendMessage(HandshakeType.session_ticket, newSessionTicketBody); + } + + // NOTE: Calculated exclusive of the Finished message itself + byte[] serverVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext, ExporterLabel.server_finished, + TlsProtocol.GetCurrentPrfHash(state.serverContext, handshake.HandshakeHash, null)); + handshake.SendMessage(HandshakeType.finished, serverVerifyData); + + handshake.Finish(); + + state.server.NotifyHandshakeComplete(); + + return new DtlsTransport(recordLayer); + } + + protected virtual byte[] GenerateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest) + { + MemoryStream buf = new MemoryStream(); + certificateRequest.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateCertificateStatus(ServerHandshakeState state, CertificateStatus certificateStatus) + { + MemoryStream buf = new MemoryStream(); + certificateStatus.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket) + { + MemoryStream buf = new MemoryStream(); + newSessionTicket.Encode(buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateServerHello(ServerHandshakeState state) + { + SecurityParameters securityParameters = state.serverContext.SecurityParameters; + + MemoryStream buf = new MemoryStream(); + + ProtocolVersion server_version = state.server.GetServerVersion(); + if (!server_version.IsEqualOrEarlierVersionOf(state.serverContext.ClientVersion)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + // TODO Read RFCs for guidance on the expected record layer version number + // recordStream.setReadVersion(server_version); + // recordStream.setWriteVersion(server_version); + // recordStream.setRestrictReadVersion(true); + state.serverContext.SetServerVersion(server_version); + + TlsUtilities.WriteVersion(state.serverContext.ServerVersion, buf); + + buf.Write(securityParameters.ServerRandom, 0, securityParameters.ServerRandom.Length); + + /* + * The server may return an empty session_id to indicate that the session will not be cached + * and therefore cannot be resumed. + */ + TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, buf); + + state.selectedCipherSuite = state.server.GetSelectedCipherSuite(); + if (!Arrays.Contains(state.offeredCipherSuites, state.selectedCipherSuite) + || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || CipherSuite.IsScsv(state.selectedCipherSuite) + || !TlsUtilities.IsValidCipherSuiteForVersion(state.selectedCipherSuite, server_version)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + ValidateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.internal_error); + + state.selectedCompressionMethod = state.server.GetSelectedCompressionMethod(); + if (!Arrays.Contains(state.offeredCompressionMethods, (byte)state.selectedCompressionMethod)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + TlsUtilities.WriteUint16(state.selectedCipherSuite, buf); + TlsUtilities.WriteUint8((byte)state.selectedCompressionMethod, buf); + + state.serverExtensions = state.server.GetServerExtensions(); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + if (state.secure_renegotiation) + { + byte[] renegExtData = TlsUtilities.GetExtensionData(state.serverExtensions, ExtensionType.renegotiation_info); + bool noRenegExt = (null == renegExtData); + + if (noRenegExt) + { + /* + * Note that sending a "renegotiation_info" extension in response to a ClientHello + * containing only the SCSV is an explicit exception to the prohibition in RFC 5246, + * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed + * because the client is signaling its willingness to receive the extension via the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. + */ + + /* + * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty + * "renegotiation_info" extension in the ServerHello message. + */ + state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(state.serverExtensions); + state.serverExtensions[ExtensionType.renegotiation_info] = TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes); + } + } + + if (securityParameters.extendedMasterSecret) + { + state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(state.serverExtensions); + TlsExtensionsUtilities.AddExtendedMasterSecretExtension(state.serverExtensions); + } + + if (state.serverExtensions != null) + { + securityParameters.encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(state.serverExtensions); + + state.maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.clientExtensions, state.serverExtensions, + AlertDescription.internal_error); + + securityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(state.serverExtensions); + + state.allowCertificateStatus = TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions, + ExtensionType.status_request, AlertDescription.internal_error); + + state.expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions, + ExtensionType.session_ticket, AlertDescription.internal_error); + + TlsProtocol.WriteExtensions(buf, state.serverExtensions); + } + + return buf.ToArray(); + } + + protected virtual void NotifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate) + { + if (state.certificateRequest == null) + throw new InvalidOperationException(); + + if (state.clientCertificate != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + state.clientCertificate = clientCertificate; + + if (clientCertificate.IsEmpty) + { + state.keyExchange.SkipClientCredentials(); + } + else + { + + /* + * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request + * message was non-empty, one of the certificates in the certificate chain SHOULD be + * issued by one of the listed CAs. + */ + + state.clientCertificateType = TlsUtilities.GetClientCertificateType(clientCertificate, + state.serverCredentials.Certificate); + + state.keyExchange.ProcessClientCertificate(clientCertificate); + } + + /* + * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its + * discretion either continue the handshake without client authentication, or respond with a + * fatal handshake_failure alert. Also, if some aspect of the certificate chain was + * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its + * discretion either continue the handshake (considering the client unauthenticated) or send + * a fatal alert. + */ + state.server.NotifyClientCertificate(clientCertificate); + } + + protected virtual void ProcessClientCertificate(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + Certificate clientCertificate = Certificate.Parse(buf); + + TlsProtocol.AssertEmpty(buf); + + NotifyClientCertificate(state, clientCertificate); + } + + protected virtual void ProcessCertificateVerify(ServerHandshakeState state, byte[] body, TlsHandshakeHash prepareFinishHash) + { + MemoryStream buf = new MemoryStream(body, false); + + TlsServerContextImpl context = state.serverContext; + DigitallySigned clientCertificateVerify = DigitallySigned.Parse(context, buf); + + TlsProtocol.AssertEmpty(buf); + + // Verify the CertificateVerify message contains a correct signature. + bool verified = false; + try + { + byte[] hash; + if (TlsUtilities.IsTlsV12(context)) + { + hash = prepareFinishHash.GetFinalHash(clientCertificateVerify.Algorithm.Hash); + } + else + { + hash = context.SecurityParameters.SessionHash; + } + + X509CertificateStructure x509Cert = state.clientCertificate.GetCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo); + + TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)state.clientCertificateType); + tlsSigner.Init(context); + verified = tlsSigner.VerifyRawSignature(clientCertificateVerify.Algorithm, + clientCertificateVerify.Signature, publicKey, hash); + } + catch (Exception) + { + } + + if (!verified) + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + + protected virtual void ProcessClientHello(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + // TODO Read RFCs for guidance on the expected record layer version number + ProtocolVersion client_version = TlsUtilities.ReadVersion(buf); + if (!client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + /* + * Read the client random + */ + byte[] client_random = TlsUtilities.ReadFully(32, buf); + + byte[] sessionID = TlsUtilities.ReadOpaque8(buf); + if (sessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347 + byte[] cookie = TlsUtilities.ReadOpaque8(buf); + + int cipher_suites_length = TlsUtilities.ReadUint16(buf); + if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + /* + * NOTE: "If the session_id field is not empty (implying a session resumption request) this + * vector must include at least the cipher_suite from that session." + */ + state.offeredCipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, buf); + + int compression_methods_length = TlsUtilities.ReadUint8(buf); + if (compression_methods_length < 1) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + state.offeredCompressionMethods = TlsUtilities.ReadUint8Array(compression_methods_length, buf); + + /* + * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello containing no + * extensions. + */ + state.clientExtensions = TlsProtocol.ReadExtensions(buf); + + TlsServerContextImpl context = state.serverContext; + SecurityParameters securityParameters = context.SecurityParameters; + + securityParameters.extendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(state.clientExtensions); + + context.SetClientVersion(client_version); + + state.server.NotifyClientVersion(client_version); + state.server.NotifyFallback(Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)); + + securityParameters.clientRandom = client_random; + + state.server.NotifyOfferedCipherSuites(state.offeredCipherSuites); + state.server.NotifyOfferedCompressionMethods(state.offeredCompressionMethods); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + { + /* + * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension, + * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the + * ClientHello. Including both is NOT RECOMMENDED. + */ + + /* + * When a ClientHello is received, the server MUST check if it includes the + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag + * to TRUE. + */ + if (Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + { + state.secure_renegotiation = true; + } + + /* + * The server MUST check if the "renegotiation_info" extension is included in the + * ClientHello. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions, ExtensionType.renegotiation_info); + if (renegExtData != null) + { + /* + * If the extension is present, set secure_renegotiation flag to TRUE. The + * server MUST then verify that the length of the "renegotiated_connection" + * field is zero, and if it is not, MUST abort the handshake. + */ + state.secure_renegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + state.server.NotifySecureRenegotiation(state.secure_renegotiation); + + if (state.clientExtensions != null) + { + state.server.ProcessClientExtensions(state.clientExtensions); + } + } + + protected virtual void ProcessClientKeyExchange(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + + state.keyExchange.ProcessClientKeyExchange(buf); + + TlsProtocol.AssertEmpty(buf); + } + + protected virtual void ProcessClientSupplementalData(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + IList clientSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf); + state.server.ProcessClientSupplementalData(clientSupplementalData); + } + + protected virtual bool ExpectCertificateVerifyMessage(ServerHandshakeState state) + { + return state.clientCertificateType >= 0 && TlsUtilities.HasSigningCapability((byte)state.clientCertificateType); + } + + protected internal class ServerHandshakeState + { + internal TlsServer server = null; + internal TlsServerContextImpl serverContext = null; + internal int[] offeredCipherSuites; + internal byte[] offeredCompressionMethods; + internal IDictionary clientExtensions; + internal int selectedCipherSuite = -1; + internal short selectedCompressionMethod = -1; + internal bool secure_renegotiation = false; + internal short maxFragmentLength = -1; + internal bool allowCertificateStatus = false; + internal bool expectSessionTicket = false; + internal IDictionary serverExtensions = null; + internal TlsKeyExchange keyExchange = null; + internal TlsCredentials serverCredentials = null; + internal CertificateRequest certificateRequest = null; + internal short clientCertificateType = -1; + internal Certificate clientCertificate = null; + } + } +} diff --git a/crypto/src/crypto/tls/DtlsTransport.cs b/crypto/src/crypto/tls/DtlsTransport.cs new file mode 100644 index 000000000..5c607336b --- /dev/null +++ b/crypto/src/crypto/tls/DtlsTransport.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DtlsTransport + : DatagramTransport + { + private readonly DtlsRecordLayer mRecordLayer; + + internal DtlsTransport(DtlsRecordLayer recordLayer) + { + this.mRecordLayer = recordLayer; + } + + public virtual int GetReceiveLimit() + { + return mRecordLayer.GetReceiveLimit(); + } + + public virtual int GetSendLimit() + { + return mRecordLayer.GetSendLimit(); + } + + public virtual int Receive(byte[] buf, int off, int len, int waitMillis) + { + try + { + return mRecordLayer.Receive(buf, off, len, waitMillis); + } + catch (TlsFatalAlert fatalAlert) + { + mRecordLayer.Fail(fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + public virtual void Send(byte[] buf, int off, int len) + { + try + { + mRecordLayer.Send(buf, off, len); + } + catch (TlsFatalAlert fatalAlert) + { + mRecordLayer.Fail(fatalAlert.AlertDescription); + throw fatalAlert; + } + catch (IOException e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw e; + } + catch (Exception e) + { + mRecordLayer.Fail(AlertDescription.internal_error); + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } + + public virtual void Close() + { + mRecordLayer.Close(); + } + } +} diff --git a/crypto/src/crypto/tls/PskTlsClient.cs b/crypto/src/crypto/tls/PskTlsClient.cs index 6063572a0..1f4b0865c 100644 --- a/crypto/src/crypto/tls/PskTlsClient.cs +++ b/crypto/src/crypto/tls/PskTlsClient.cs @@ -3,7 +3,7 @@ using System.Collections; namespace Org.BouncyCastle.Crypto.Tls { - public abstract class PskTlsClient + public class PskTlsClient : AbstractTlsClient { protected TlsPskIdentity mPskIdentity; @@ -25,8 +25,8 @@ namespace Org.BouncyCastle.Crypto.Tls { CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, - CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, - CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA }; } @@ -124,6 +124,15 @@ namespace Org.BouncyCastle.Crypto.Tls } } + public override TlsAuthentication GetAuthentication() + { + /* + * Note: This method is not called unless a server certificate is sent, which may be the + * case e.g. for RSA_PSK key exchange. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + public override TlsCipher GetCipher() { switch (mSelectedCipherSuite) @@ -254,7 +263,7 @@ namespace Org.BouncyCastle.Crypto.Tls protected virtual TlsKeyExchange CreatePskKeyExchange(int keyExchange) { - return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mPskIdentity, null, mNamedCurves, + return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, mPskIdentity, null, null, mNamedCurves, mClientECPointFormats, mServerECPointFormats); } } diff --git a/crypto/src/crypto/tls/PskTlsServer.cs b/crypto/src/crypto/tls/PskTlsServer.cs new file mode 100644 index 000000000..bdb8b74a5 --- /dev/null +++ b/crypto/src/crypto/tls/PskTlsServer.cs @@ -0,0 +1,347 @@ +using System; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class PskTlsServer + : AbstractTlsServer + { + protected TlsPskIdentityManager mPskIdentityManager; + + public PskTlsServer(TlsPskIdentityManager pskIdentityManager) + : this(new DefaultTlsCipherFactory(), pskIdentityManager) + { + } + + public PskTlsServer(TlsCipherFactory cipherFactory, TlsPskIdentityManager pskIdentityManager) + : base(cipherFactory) + { + this.mPskIdentityManager = pskIdentityManager; + } + + protected virtual TlsEncryptionCredentials GetRsaEncryptionCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + protected virtual DHParameters GetDHParameters() + { + return DHStandardGroups.rfc5114_1024_160; + } + + protected override int[] GetCipherSuites() + { + return new int[] + { + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA + }; + } + + public override TlsCredentials GetCredentials() + { + switch (mSelectedCipherSuite) + { + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1: + + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1: + return null; + + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1: + return GetRsaEncryptionCredentials(); + + default: + /* Note: internal error here; selected a key exchange we don't implement! */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsKeyExchange GetKeyExchange() + { + switch (mSelectedCipherSuite) + { + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + return CreatePskKeyExchange(KeyExchangeAlgorithm.DHE_PSK); + + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1: + return CreatePskKeyExchange(KeyExchangeAlgorithm.ECDHE_PSK); + + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1: + return CreatePskKeyExchange(KeyExchangeAlgorithm.PSK); + + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1: + return CreatePskKeyExchange(KeyExchangeAlgorithm.RSA_PSK); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public override TlsCipher GetCipher() + { + switch (mSelectedCipherSuite) + { + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.cls_3DES_EDE_CBC, MacAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CBC, MacAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM, MacAlgorithm.cls_null); + + case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_CCM_8, MacAlgorithm.cls_null); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_128_GCM, MacAlgorithm.cls_null); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CBC, MacAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM, MacAlgorithm.cls_null); + + case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: + case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_CCM_8, MacAlgorithm.cls_null); + + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.AES_256_GCM, MacAlgorithm.cls_null); + + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_CBC, MacAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_128_GCM, MacAlgorithm.cls_null); + + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_CBC, MacAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.CAMELLIA_256_GCM, MacAlgorithm.cls_null); + + case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.ESTREAM_SALSA20, MacAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_PSK_WITH_NULL_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_PSK_WITH_NULL_SHA256: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha256); + + case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_PSK_WITH_NULL_SHA384: + case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.NULL, MacAlgorithm.hmac_sha384); + + case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_PSK_WITH_RC4_128_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.RC4_128, MacAlgorithm.hmac_sha1); + + case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1: + case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1: + return mCipherFactory.CreateCipher(mContext, EncryptionAlgorithm.SALSA20, MacAlgorithm.hmac_sha1); + + default: + /* + * Note: internal error here; the TlsProtocol implementation verifies that the + * server-selected cipher suite was in the list of client-offered cipher suites, so if + * we now can't produce an implementation, we shouldn't have offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected TlsKeyExchange CreatePskKeyExchange(int keyExchange) + { + return new TlsPskKeyExchange(keyExchange, mSupportedSignatureAlgorithms, null, mPskIdentityManager, + GetDHParameters(), mNamedCurves, mClientECPointFormats, mServerECPointFormats); + } + } +} diff --git a/crypto/src/crypto/tls/SecurityParameters.cs b/crypto/src/crypto/tls/SecurityParameters.cs index 12bb59f22..0f48ee23e 100644 --- a/crypto/src/crypto/tls/SecurityParameters.cs +++ b/crypto/src/crypto/tls/SecurityParameters.cs @@ -15,6 +15,7 @@ namespace Org.BouncyCastle.Crypto.Tls internal byte[] clientRandom = null; internal byte[] serverRandom = null; internal byte[] sessionHash = null; + internal byte[] pskIdentity = null; // TODO Keep these internal, since it's maybe not the ideal place for them internal short maxFragmentLength = -1; @@ -87,5 +88,10 @@ namespace Org.BouncyCastle.Crypto.Tls { get { return sessionHash; } } + + public virtual byte[] PskIdentity + { + get { return pskIdentity; } + } } } diff --git a/crypto/src/crypto/tls/ServerDHParams.cs b/crypto/src/crypto/tls/ServerDHParams.cs index 381858854..b09262771 100644 --- a/crypto/src/crypto/tls/ServerDHParams.cs +++ b/crypto/src/crypto/tls/ServerDHParams.cs @@ -54,7 +54,8 @@ namespace Org.BouncyCastle.Crypto.Tls BigInteger g = TlsDHUtilities.ReadDHParameter(input); BigInteger Ys = TlsDHUtilities.ReadDHParameter(input); - return new ServerDHParams(new DHPublicKeyParameters(Ys, new DHParameters(p, g))); + return new ServerDHParams( + TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Ys, new DHParameters(p, g)))); } } } diff --git a/crypto/src/crypto/tls/SessionParameters.cs b/crypto/src/crypto/tls/SessionParameters.cs index c4616ac71..b17e931d7 100644 --- a/crypto/src/crypto/tls/SessionParameters.cs +++ b/crypto/src/crypto/tls/SessionParameters.cs @@ -14,6 +14,7 @@ namespace Org.BouncyCastle.Crypto.Tls private short mCompressionAlgorithm = -1; private byte[] mMasterSecret = null; private Certificate mPeerCertificate = null; + private byte[] mPskIdentity = null; private byte[] mEncodedServerExtensions = null; public Builder() @@ -26,7 +27,7 @@ namespace Org.BouncyCastle.Crypto.Tls Validate(this.mCompressionAlgorithm >= 0, "compressionAlgorithm"); Validate(this.mMasterSecret != null, "masterSecret"); return new SessionParameters(mCipherSuite, (byte)mCompressionAlgorithm, mMasterSecret, mPeerCertificate, - mEncodedServerExtensions); + mPskIdentity, mEncodedServerExtensions); } public Builder SetCipherSuite(int cipherSuite) @@ -53,6 +54,12 @@ namespace Org.BouncyCastle.Crypto.Tls return this; } + public Builder SetPskIdentity(byte[] pskIdentity) + { + this.mPskIdentity = pskIdentity; + return this; + } + public Builder SetServerExtensions(IDictionary serverExtensions) { if (serverExtensions == null) @@ -79,15 +86,17 @@ namespace Org.BouncyCastle.Crypto.Tls private byte mCompressionAlgorithm; private byte[] mMasterSecret; private Certificate mPeerCertificate; + private byte[] mPskIdentity; private byte[] mEncodedServerExtensions; private SessionParameters(int cipherSuite, byte compressionAlgorithm, byte[] masterSecret, - Certificate peerCertificate, byte[] encodedServerExtensions) + Certificate peerCertificate, byte[] pskIdentity, byte[] encodedServerExtensions) { this.mCipherSuite = cipherSuite; this.mCompressionAlgorithm = compressionAlgorithm; this.mMasterSecret = Arrays.Clone(masterSecret); this.mPeerCertificate = peerCertificate; + this.mPskIdentity = Arrays.Clone(pskIdentity); this.mEncodedServerExtensions = encodedServerExtensions; } @@ -102,7 +111,7 @@ namespace Org.BouncyCastle.Crypto.Tls public SessionParameters Copy() { return new SessionParameters(mCipherSuite, mCompressionAlgorithm, mMasterSecret, mPeerCertificate, - mEncodedServerExtensions); + mPskIdentity, mEncodedServerExtensions); } public int CipherSuite @@ -125,6 +134,11 @@ namespace Org.BouncyCastle.Crypto.Tls get { return mPeerCertificate; } } + public byte[] PskIdentity + { + get { return mPskIdentity; } + } + public IDictionary ReadServerExtensions() { if (mEncodedServerExtensions == null) diff --git a/crypto/src/crypto/tls/TlsBlockCipher.cs b/crypto/src/crypto/tls/TlsBlockCipher.cs index 82c0318b2..d81b881fc 100644 --- a/crypto/src/crypto/tls/TlsBlockCipher.cs +++ b/crypto/src/crypto/tls/TlsBlockCipher.cs @@ -262,10 +262,19 @@ namespace Org.BouncyCastle.Crypto.Tls byte[] receivedMac = Arrays.CopyOfRange(ciphertext, end - macSize, end); byte[] calculatedMac = mReadMac.CalculateMac(seqNo, type, ciphertext, offset, len - macSize); - bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); - - if (badMac) + bool badMacEtm = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); + if (badMacEtm) + { + /* + * RFC 7366 3. The MAC SHALL be evaluated before any further processing such as + * decryption is performed, and if the MAC verification fails, then processing SHALL + * terminate immediately. For TLS, a fatal bad_record_mac MUST be generated [2]. For + * DTLS, the record MUST be discarded, and a fatal bad_record_mac MAY be generated + * [4]. This immediate response to a bad MAC eliminates any timing channels that may + * be available through the use of manipulated packet data. + */ throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } } if (useExplicitIV) @@ -283,6 +292,7 @@ namespace Org.BouncyCastle.Crypto.Tls // If there's anything wrong with the padding, this will return zero int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMac ? 0 : macSize); + bool badMac = (totalPad == 0); int dec_output_length = blocks_length - totalPad; @@ -295,14 +305,12 @@ namespace Org.BouncyCastle.Crypto.Tls byte[] calculatedMac = mReadMac.CalculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen, blocks_length - macSize, randomData); - bool badMac = !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); - - if (badMac || totalPad == 0) - { - throw new TlsFatalAlert(AlertDescription.bad_record_mac); - } + badMac |= !Arrays.ConstantTimeAreEqual(calculatedMac, receivedMac); } + if (badMac) + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + return Arrays.CopyOfRange(ciphertext, offset, offset + dec_output_length); } diff --git a/crypto/src/crypto/tls/TlsClient.cs b/crypto/src/crypto/tls/TlsClient.cs index cd5dfad13..116f6a779 100644 --- a/crypto/src/crypto/tls/TlsClient.cs +++ b/crypto/src/crypto/tls/TlsClient.cs @@ -27,6 +27,8 @@ namespace Org.BouncyCastle.Crypto.Tls ProtocolVersion ClientVersion { get; } + bool IsFallback { get; } + /// <summary> /// Get the list of cipher suites that this client supports. /// </summary> diff --git a/crypto/src/crypto/tls/TlsClientProtocol.cs b/crypto/src/crypto/tls/TlsClientProtocol.cs index 9fe50add8..19e7d71aa 100644 --- a/crypto/src/crypto/tls/TlsClientProtocol.cs +++ b/crypto/src/crypto/tls/TlsClientProtocol.cs @@ -607,7 +607,7 @@ namespace Org.BouncyCastle.Crypto.Tls int selectedCipherSuite = TlsUtilities.ReadUint16(buf); if (!Arrays.Contains(this.mOfferedCipherSuites, selectedCipherSuite) || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL - || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV + || CipherSuite.IsScsv(selectedCipherSuite) || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version)) { throw new TlsFatalAlert(AlertDescription.illegal_parameter); @@ -815,6 +815,8 @@ namespace Org.BouncyCastle.Crypto.Tls } } + bool fallback = this.mTlsClient.IsFallback; + this.mOfferedCipherSuites = this.mTlsClient.GetCipherSuites(); this.mOfferedCompressionMethods = this.mTlsClient.GetCompressionMethods(); @@ -850,9 +852,9 @@ namespace Org.BouncyCastle.Crypto.Tls byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, ExtensionType.renegotiation_info); bool noRenegExt = (null == renegExtData); - bool noSCSV = !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); + bool noRenegScsv = !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); - if (noRenegExt && noSCSV) + if (noRenegExt && noRenegScsv) { // TODO Consider whether to default to a client extension instead // this.mClientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(this.mClientExtensions); @@ -860,6 +862,17 @@ namespace Org.BouncyCastle.Crypto.Tls this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV); } + /* + * draft-ietf-tls-downgrade-scsv-00 4. If a client sends a ClientHello.client_version + * containing a lower value than the latest (highest-valued) version supported by the + * client, it SHOULD include the TLS_FALLBACK_SCSV cipher suite value in + * ClientHello.cipher_suites. + */ + if (fallback && !Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)) + { + this.mOfferedCipherSuites = Arrays.Append(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV); + } + TlsUtilities.WriteUint16ArrayWithUint16Length(mOfferedCipherSuites, message); } diff --git a/crypto/src/crypto/tls/TlsECDheKeyExchange.cs b/crypto/src/crypto/tls/TlsECDheKeyExchange.cs index 0644bd44d..b99db0c18 100644 --- a/crypto/src/crypto/tls/TlsECDheKeyExchange.cs +++ b/crypto/src/crypto/tls/TlsECDheKeyExchange.cs @@ -34,73 +34,10 @@ namespace Org.BouncyCastle.Crypto.Tls public override byte[] GenerateServerKeyExchange() { - /* - * First we try to find a supported named curve from the client's list. - */ - int namedCurve = -1; - if (mNamedCurves == null) - { - // TODO Let the peer choose the default named curve - namedCurve = NamedCurve.secp256r1; - } - else - { - for (int i = 0; i < mNamedCurves.Length; ++i) - { - int entry = mNamedCurves[i]; - if (NamedCurve.IsValid(entry) && TlsEccUtilities.IsSupportedNamedCurve(entry)) - { - namedCurve = entry; - break; - } - } - } - - ECDomainParameters curve_params = null; - if (namedCurve >= 0) - { - curve_params = TlsEccUtilities.GetParametersForNamedCurve(namedCurve); - } - else - { - /* - * If no named curves are suitable, check if the client supports explicit curves. - */ - if (Arrays.Contains(mNamedCurves, NamedCurve.arbitrary_explicit_prime_curves)) - { - curve_params = TlsEccUtilities.GetParametersForNamedCurve(NamedCurve.secp256r1); - } - else if (Arrays.Contains(mNamedCurves, NamedCurve.arbitrary_explicit_char2_curves)) - { - curve_params = TlsEccUtilities.GetParametersForNamedCurve(NamedCurve.sect283r1); - } - } - - if (curve_params == null) - { - /* - * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find - * a suitable curve. - */ - throw new TlsFatalAlert(AlertDescription.internal_error); - } - - AsymmetricCipherKeyPair kp = TlsEccUtilities.GenerateECKeyPair(context.SecureRandom, curve_params); - this.mECAgreePrivateKey = (ECPrivateKeyParameters)kp.Private; - DigestInputBuffer buf = new DigestInputBuffer(); - if (namedCurve < 0) - { - TlsEccUtilities.WriteExplicitECParameters(mClientECPointFormats, curve_params, buf); - } - else - { - TlsEccUtilities.WriteNamedECParameters(namedCurve, buf); - } - - ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters)kp.Public; - TlsEccUtilities.WriteECPoint(mClientECPointFormats, ecPublicKey.Q, buf); + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(context.SecureRandom, mNamedCurves, + mClientECPointFormats, buf); /* * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2 diff --git a/crypto/src/crypto/tls/TlsEccUtilities.cs b/crypto/src/crypto/tls/TlsEccUtilities.cs index 64c3c1593..e938b1685 100644 --- a/crypto/src/crypto/tls/TlsEccUtilities.cs +++ b/crypto/src/crypto/tls/TlsEccUtilities.cs @@ -246,6 +246,14 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: /* + * RFC 7251 + */ + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: + + /* * draft-agl-tls-chacha20poly1305-04 */ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: @@ -427,6 +435,69 @@ namespace Org.BouncyCastle.Crypto.Tls return (ECPrivateKeyParameters)kp.Private; } + // TODO Refactor around ServerECDHParams before making this public + internal static ECPrivateKeyParameters GenerateEphemeralServerKeyExchange(SecureRandom random, int[] namedCurves, + byte[] ecPointFormats, Stream output) + { + /* First we try to find a supported named curve from the client's list. */ + int namedCurve = -1; + if (namedCurves == null) + { + // TODO Let the peer choose the default named curve + namedCurve = NamedCurve.secp256r1; + } + else + { + for (int i = 0; i < namedCurves.Length; ++i) + { + int entry = namedCurves[i]; + if (NamedCurve.IsValid(entry) && IsSupportedNamedCurve(entry)) + { + namedCurve = entry; + break; + } + } + } + + ECDomainParameters ecParams = null; + if (namedCurve >= 0) + { + ecParams = GetParametersForNamedCurve(namedCurve); + } + else + { + /* If no named curves are suitable, check if the client supports explicit curves. */ + if (Arrays.Contains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves)) + { + ecParams = GetParametersForNamedCurve(NamedCurve.secp256r1); + } + else if (Arrays.Contains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves)) + { + ecParams = GetParametersForNamedCurve(NamedCurve.sect283r1); + } + } + + if (ecParams == null) + { + /* + * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find + * a suitable curve. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + if (namedCurve < 0) + { + WriteExplicitECParameters(ecPointFormats, ecParams, output); + } + else + { + WriteNamedECParameters(namedCurve, output); + } + + return GenerateEphemeralClientKeyExchange(random, ecPointFormats, ecParams, output); + } + public static ECPublicKeyParameters ValidateECPublicKey(ECPublicKeyParameters key) { // TODO Check RFC 4492 for validation diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs index 8ba156952..09838a717 100644 --- a/crypto/src/crypto/tls/TlsProtocol.cs +++ b/crypto/src/crypto/tls/TlsProtocol.cs @@ -956,7 +956,11 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: diff --git a/crypto/src/crypto/tls/TlsPskIdentityManager.cs b/crypto/src/crypto/tls/TlsPskIdentityManager.cs new file mode 100644 index 000000000..a72c2299c --- /dev/null +++ b/crypto/src/crypto/tls/TlsPskIdentityManager.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsPskIdentityManager + { + byte[] GetHint(); + + byte[] GetPsk(byte[] identity); + } +} diff --git a/crypto/src/crypto/tls/TlsPskKeyExchange.cs b/crypto/src/crypto/tls/TlsPskKeyExchange.cs index cd13e3438..a8d0867ef 100644 --- a/crypto/src/crypto/tls/TlsPskKeyExchange.cs +++ b/crypto/src/crypto/tls/TlsPskKeyExchange.cs @@ -4,7 +4,10 @@ using System.IO; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; namespace Org.BouncyCastle.Crypto.Tls { @@ -13,22 +16,29 @@ namespace Org.BouncyCastle.Crypto.Tls : AbstractTlsKeyExchange { protected TlsPskIdentity mPskIdentity; + protected TlsPskIdentityManager mPskIdentityManager; + protected DHParameters mDHParameters; protected int[] mNamedCurves; protected byte[] mClientECPointFormats, mServerECPointFormats; protected byte[] mPskIdentityHint = null; + protected byte[] mPsk = null; protected DHPrivateKeyParameters mDHAgreePrivateKey = null; protected DHPublicKeyParameters mDHAgreePublicKey = null; + protected ECPrivateKeyParameters mECAgreePrivateKey = null; + protected ECPublicKeyParameters mECAgreePublicKey = null; + protected AsymmetricKeyParameter mServerPublicKey = null; protected RsaKeyParameters mRsaServerPublicKey = null; protected TlsEncryptionCredentials mServerCredentials = null; protected byte[] mPremasterSecret; public TlsPskKeyExchange(int keyExchange, IList supportedSignatureAlgorithms, TlsPskIdentity pskIdentity, - DHParameters dhParameters, int[] namedCurves, byte[] clientECPointFormats, byte[] serverECPointFormats) + TlsPskIdentityManager pskIdentityManager, DHParameters dhParameters, int[] namedCurves, + byte[] clientECPointFormats, byte[] serverECPointFormats) : base(keyExchange, supportedSignatureAlgorithms) { switch (keyExchange) @@ -43,6 +53,7 @@ namespace Org.BouncyCastle.Crypto.Tls } this.mPskIdentity = pskIdentity; + this.mPskIdentityManager = pskIdentityManager; this.mDHParameters = dhParameters; this.mNamedCurves = namedCurves; this.mClientECPointFormats = clientECPointFormats; @@ -67,8 +78,7 @@ namespace Org.BouncyCastle.Crypto.Tls public override byte[] GenerateServerKeyExchange() { - // TODO[RFC 4279] Need a server-side PSK API to determine hint and resolve identities to keys - this.mPskIdentityHint = null; + this.mPskIdentityHint = mPskIdentityManager.GetHint(); if (this.mPskIdentityHint == null && !RequiresServerKeyExchange) return null; @@ -94,7 +104,8 @@ namespace Org.BouncyCastle.Crypto.Tls } else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) { - // TODO[RFC 5489] + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralServerKeyExchange(context.SecureRandom, + mNamedCurves, mClientECPointFormats, buf); } return buf.ToArray(); @@ -157,7 +168,12 @@ namespace Org.BouncyCastle.Crypto.Tls } else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) { - // TODO[RFC 5489] + ECDomainParameters ecParams = TlsEccUtilities.ReadECParameters(mNamedCurves, mClientECPointFormats, input); + + byte[] point = TlsUtilities.ReadOpaque8(input); + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mClientECPointFormats, ecParams, point)); } } @@ -183,9 +199,17 @@ namespace Org.BouncyCastle.Crypto.Tls } byte[] psk_identity = mPskIdentity.GetPskIdentity(); + if (psk_identity == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.mPsk = mPskIdentity.GetPsk(); + if (mPsk == null) + throw new TlsFatalAlert(AlertDescription.internal_error); TlsUtilities.WriteOpaque16(psk_identity, output); + context.SecurityParameters.pskIdentity = psk_identity; + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) { this.mDHAgreePrivateKey = TlsDHUtilities.GenerateEphemeralClientKeyExchange(context.SecureRandom, @@ -193,8 +217,8 @@ namespace Org.BouncyCastle.Crypto.Tls } else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) { - // TODO[RFC 5489] - throw new TlsFatalAlert(AlertDescription.internal_error); + this.mECAgreePrivateKey = TlsEccUtilities.GenerateEphemeralClientKeyExchange(context.SecureRandom, + mServerECPointFormats, mECAgreePublicKey.Parameters, output); } else if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK) { @@ -203,14 +227,59 @@ namespace Org.BouncyCastle.Crypto.Tls } } + public override void ProcessClientKeyExchange(Stream input) + { + byte[] psk_identity = TlsUtilities.ReadOpaque16(input); + + this.mPsk = mPskIdentityManager.GetPsk(psk_identity); + if (mPsk == null) + throw new TlsFatalAlert(AlertDescription.unknown_psk_identity); + + context.SecurityParameters.pskIdentity = psk_identity; + + if (this.mKeyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + BigInteger Yc = TlsDHUtilities.ReadDHParameter(input); + + this.mDHAgreePublicKey = TlsDHUtilities.ValidateDHPublicKey(new DHPublicKeyParameters(Yc, mDHParameters)); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) + { + byte[] point = TlsUtilities.ReadOpaque8(input); + + ECDomainParameters curve_params = this.mECAgreePrivateKey.Parameters; + + this.mECAgreePublicKey = TlsEccUtilities.ValidateECPublicKey(TlsEccUtilities.DeserializeECPublicKey( + mServerECPointFormats, curve_params, point)); + } + else if (this.mKeyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + byte[] encryptedPreMasterSecret; + if (TlsUtilities.IsSsl(context)) + { + // TODO Do any SSLv3 clients actually include the length? + encryptedPreMasterSecret = Streams.ReadAll(input); + } + else + { + encryptedPreMasterSecret = TlsUtilities.ReadOpaque16(input); + } + + this.mPremasterSecret = mServerCredentials.DecryptPreMasterSecret(encryptedPreMasterSecret); + } + } + public override byte[] GeneratePremasterSecret() { - byte[] psk = mPskIdentity.GetPsk(); - byte[] other_secret = GenerateOtherSecret(psk.Length); + byte[] other_secret = GenerateOtherSecret(mPsk.Length); - MemoryStream buf = new MemoryStream(4 + other_secret.Length + psk.Length); + MemoryStream buf = new MemoryStream(4 + other_secret.Length + mPsk.Length); TlsUtilities.WriteOpaque16(other_secret, buf); - TlsUtilities.WriteOpaque16(psk, buf); + TlsUtilities.WriteOpaque16(mPsk, buf); + + Arrays.Fill(mPsk, (byte)0); + this.mPsk = null; + return buf.ToArray(); } @@ -228,7 +297,11 @@ namespace Org.BouncyCastle.Crypto.Tls if (this.mKeyExchange == KeyExchangeAlgorithm.ECDHE_PSK) { - // TODO[RFC 5489] + if (mECAgreePrivateKey != null) + { + return TlsEccUtilities.CalculateECDHBasicAgreement(mECAgreePublicKey, mECAgreePrivateKey); + } + throw new TlsFatalAlert(AlertDescription.internal_error); } diff --git a/crypto/src/crypto/tls/TlsServer.cs b/crypto/src/crypto/tls/TlsServer.cs index 93e62b9ac..e791f93a9 100644 --- a/crypto/src/crypto/tls/TlsServer.cs +++ b/crypto/src/crypto/tls/TlsServer.cs @@ -13,6 +13,9 @@ namespace Org.BouncyCastle.Crypto.Tls void NotifyClientVersion(ProtocolVersion clientVersion); /// <exception cref="IOException"></exception> + void NotifyFallback(bool isFallback); + + /// <exception cref="IOException"></exception> void NotifyOfferedCipherSuites(int[] offeredCipherSuites); /// <exception cref="IOException"></exception> diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs index 165d6a147..b1fb830b6 100644 --- a/crypto/src/crypto/tls/TlsServerProtocol.cs +++ b/crypto/src/crypto/tls/TlsServerProtocol.cs @@ -453,6 +453,8 @@ namespace Org.BouncyCastle.Crypto.Tls protected virtual void ReceiveClientHelloMessage(MemoryStream buf) { ProtocolVersion client_version = TlsUtilities.ReadVersion(buf); + mRecordStream.SetWriteVersion(client_version); + if (client_version.IsDtls) throw new TlsFatalAlert(AlertDescription.illegal_parameter); @@ -499,6 +501,7 @@ namespace Org.BouncyCastle.Crypto.Tls ContextAdmin.SetClientVersion(client_version); mTlsServer.NotifyClientVersion(client_version); + mTlsServer.NotifyFallback(Arrays.Contains(mOfferedCipherSuites, CipherSuite.TLS_FALLBACK_SCSV)); mSecurityParameters.clientRandom = client_random; @@ -626,7 +629,7 @@ namespace Org.BouncyCastle.Crypto.Tls int selectedCipherSuite = mTlsServer.GetSelectedCipherSuite(); if (!Arrays.Contains(mOfferedCipherSuites, selectedCipherSuite) || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL - || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV + || CipherSuite.IsScsv(selectedCipherSuite) || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version)) { throw new TlsFatalAlert(AlertDescription.internal_error); diff --git a/crypto/src/crypto/tls/TlsUtilities.cs b/crypto/src/crypto/tls/TlsUtilities.cs index d571e5900..485ecb760 100644 --- a/crypto/src/crypto/tls/TlsUtilities.cs +++ b/crypto/src/crypto/tls/TlsUtilities.cs @@ -1252,11 +1252,13 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: case CipherSuite.TLS_PSK_WITH_AES_128_CCM: case CipherSuite.TLS_RSA_WITH_AES_128_CCM: return EncryptionAlgorithm.AES_128_CCM; case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: @@ -1313,11 +1315,13 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: case CipherSuite.TLS_PSK_WITH_AES_256_CCM: case CipherSuite.TLS_RSA_WITH_AES_256_CCM: return EncryptionAlgorithm.AES_256_CCM; case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: @@ -1554,8 +1558,12 @@ namespace Org.BouncyCastle.Crypto.Tls case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: diff --git a/crypto/src/math/ec/ECAlgorithms.cs b/crypto/src/math/ec/ECAlgorithms.cs index 3c911b173..a1349a9e0 100644 --- a/crypto/src/math/ec/ECAlgorithms.cs +++ b/crypto/src/math/ec/ECAlgorithms.cs @@ -117,6 +117,11 @@ namespace Org.BouncyCastle.Math.EC public static void MontgomeryTrick(ECFieldElement[] zs, int off, int len) { + MontgomeryTrick(zs, off, len, null); + } + + public static void MontgomeryTrick(ECFieldElement[] zs, int off, int len, ECFieldElement scale) + { /* * Uses the "Montgomery Trick" to invert many field elements, with only a single actual * field inversion. See e.g. the paper: @@ -133,7 +138,14 @@ namespace Org.BouncyCastle.Math.EC c[i] = c[i - 1].Multiply(zs[off + i]); } - ECFieldElement u = c[--i].Invert(); + --i; + + if (scale != null) + { + c[i] = c[i].Multiply(scale); + } + + ECFieldElement u = c[i].Invert(); while (i > 0) { diff --git a/crypto/src/math/ec/ECCurve.cs b/crypto/src/math/ec/ECCurve.cs index eaa3e0c3d..339d37f7c 100644 --- a/crypto/src/math/ec/ECCurve.cs +++ b/crypto/src/math/ec/ECCurve.cs @@ -221,26 +221,56 @@ namespace Org.BouncyCastle.Math.EC */ public virtual void NormalizeAll(ECPoint[] points) { - CheckPoints(points); + NormalizeAll(points, 0, points.Length, null); + } + + /** + * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. Where more + * than one point is to be normalized, this method will generally be more efficient than + * normalizing each point separately. An (optional) z-scaling factor can be applied; effectively + * each z coordinate is scaled by this value prior to normalization (but only one + * actual multiplication is needed). + * + * @param points + * An array of points that will be updated in place with their normalized versions, + * where necessary + * @param off + * The start of the range of points to normalize + * @param len + * The length of the range of points to normalize + * @param iso + * The (optional) z-scaling factor - can be null + */ + public virtual void NormalizeAll(ECPoint[] points, int off, int len, ECFieldElement iso) + { + CheckPoints(points, off, len); - if (this.CoordinateSystem == ECCurve.COORD_AFFINE) + switch (this.CoordinateSystem) { - return; + case ECCurve.COORD_AFFINE: + case ECCurve.COORD_LAMBDA_AFFINE: + { + if (iso != null) + throw new ArgumentException("not valid for affine coordinates", "iso"); + + return; + } } /* * Figure out which of the points actually need to be normalized */ - ECFieldElement[] zs = new ECFieldElement[points.Length]; - int[] indices = new int[points.Length]; + ECFieldElement[] zs = new ECFieldElement[len]; + int[] indices = new int[len]; int count = 0; - for (int i = 0; i < points.Length; ++i) + for (int i = 0; i < len; ++i) { - ECPoint p = points[i]; - if (null != p && !p.IsNormalized()) + ECPoint p = points[off + i]; + if (null != p && (iso != null || !p.IsNormalized())) { zs[count] = p.GetZCoord(0); - indices[count++] = i; + indices[count++] = off + i; } } @@ -249,7 +279,7 @@ namespace Org.BouncyCastle.Math.EC return; } - ECAlgorithms.MontgomeryTrick(zs, 0, count); + ECAlgorithms.MontgomeryTrick(zs, 0, count, iso); for (int j = 0; j < count; ++j) { @@ -298,12 +328,19 @@ namespace Org.BouncyCastle.Math.EC protected virtual void CheckPoints(ECPoint[] points) { + CheckPoints(points, 0, points.Length); + } + + protected virtual void CheckPoints(ECPoint[] points, int off, int len) + { if (points == null) throw new ArgumentNullException("points"); + if (off < 0 || len < 0 || (off > (points.Length - len))) + throw new ArgumentException("invalid range specified", "points"); - for (int i = 0; i < points.Length; ++i) + for (int i = 0; i < len; ++i) { - ECPoint point = points[i]; + ECPoint point = points[off + i]; if (null != point && this != point.Curve) throw new ArgumentException("entries must be null or on this curve", "points"); } diff --git a/crypto/src/math/ec/multiplier/WNafUtilities.cs b/crypto/src/math/ec/multiplier/WNafUtilities.cs index 865b9073e..5491297d7 100644 --- a/crypto/src/math/ec/multiplier/WNafUtilities.cs +++ b/crypto/src/math/ec/multiplier/WNafUtilities.cs @@ -10,6 +10,7 @@ namespace Org.BouncyCastle.Math.EC.Multiplier private static readonly byte[] EMPTY_BYTES = new byte[0]; private static readonly int[] EMPTY_INTS = new int[0]; + private static readonly ECPoint[] EMPTY_POINTS = new ECPoint[0]; public static int[] GenerateCompactNaf(BigInteger k) { @@ -368,46 +369,100 @@ namespace Org.BouncyCastle.Math.EC.Multiplier { ECCurve c = p.Curve; WNafPreCompInfo wnafPreCompInfo = GetWNafPreCompInfo(c.GetPreCompInfo(p, PRECOMP_NAME)); - + + int iniPreCompLen = 0, reqPreCompLen = 1 << System.Math.Max(0, width - 2); + ECPoint[] preComp = wnafPreCompInfo.PreComp; if (preComp == null) { - preComp = new ECPoint[]{ p }; + preComp = EMPTY_POINTS; + } + else + { + iniPreCompLen = preComp.Length; } - int preCompLen = preComp.Length; - int reqPreCompLen = 1 << System.Math.Max(0, width - 2); - - if (preCompLen < reqPreCompLen) + if (iniPreCompLen < reqPreCompLen) { preComp = ResizeTable(preComp, reqPreCompLen); - if (reqPreCompLen == 2) + + if (reqPreCompLen == 1) { - preComp[1] = preComp[0].ThreeTimes(); + preComp[0] = p.Normalize(); } else { - ECPoint twiceP = wnafPreCompInfo.Twice; - if (twiceP == null) + int curPreCompLen = iniPreCompLen; + if (curPreCompLen == 0) { - twiceP = preComp[0].Twice(); - wnafPreCompInfo.Twice = twiceP; + preComp[0] = p; + curPreCompLen = 1; } - for (int i = preCompLen; i < reqPreCompLen; i++) + ECFieldElement iso = null; + + if (reqPreCompLen == 2) { - /* - * Compute the new ECPoints for the precomputation array. The values 1, 3, 5, ..., - * 2^(width-1)-1 times p are computed - */ - preComp[i] = twiceP.Add(preComp[i - 1]); + preComp[1] = p.ThreeTimes(); + } + else + { + ECPoint twiceP = wnafPreCompInfo.Twice, last = preComp[curPreCompLen - 1]; + if (twiceP == null) + { + twiceP = preComp[0].Twice(); + wnafPreCompInfo.Twice = twiceP; + + /* + * For Fp curves with Jacobian projective coordinates, use a (quasi-)isomorphism + * where 'twiceP' is "affine", so that the subsequent additions are cheaper. This + * also requires scaling the initial point's X, Y coordinates, and reversing the + * isomorphism as part of the subsequent normalization. + * + * NOTE: The correctness of this optimization depends on: + * 1) additions do not use the curve's A, B coefficients. + * 2) no special cases (i.e. Q +/- Q) when calculating 1P, 3P, 5P, ... + */ + if (ECAlgorithms.IsFpCurve(c) && c.FieldSize >= 64) + { + switch (c.CoordinateSystem) + { + case ECCurve.COORD_JACOBIAN: + case ECCurve.COORD_JACOBIAN_CHUDNOVSKY: + case ECCurve.COORD_JACOBIAN_MODIFIED: + { + iso = twiceP.GetZCoord(0); + twiceP = c.CreatePoint(twiceP.XCoord.ToBigInteger(), + twiceP.YCoord.ToBigInteger()); + + ECFieldElement iso2 = iso.Square(), iso3 = iso2.Multiply(iso); + last = last.ScaleX(iso2).ScaleY(iso3); + + if (iniPreCompLen == 0) + { + preComp[0] = last; + } + break; + } + } + } + } + + while (curPreCompLen < reqPreCompLen) + { + /* + * Compute the new ECPoints for the precomputation array. The values 1, 3, + * 5, ..., 2^(width-1)-1 times p are computed + */ + preComp[curPreCompLen++] = last = last.Add(twiceP); + } } - } - /* - * Having oft-used operands in affine form makes operations faster. - */ - c.NormalizeAll(preComp); + /* + * Having oft-used operands in affine form makes operations faster. + */ + c.NormalizeAll(preComp, iniPreCompLen, reqPreCompLen - iniPreCompLen, iso); + } } wnafPreCompInfo.PreComp = preComp; diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index b0720146c..c6b2e9e0e 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -11,22 +11,22 @@ using Org.BouncyCastle.Utilities.Collections; namespace Org.BouncyCastle.Bcpg.OpenPgp { - /// <remarks>General class to handle a PGP public key object.</remarks> + /// <remarks>General class to handle a PGP public key object.</remarks> public class PgpPublicKey { - private static readonly int[] MasterKeyCertificationTypes = new int[] - { - PgpSignature.PositiveCertification, - PgpSignature.CasualCertification, - PgpSignature.NoCertification, - PgpSignature.DefaultCertification - }; - - private long keyId; + private static readonly int[] MasterKeyCertificationTypes = new int[] + { + PgpSignature.PositiveCertification, + PgpSignature.CasualCertification, + PgpSignature.NoCertification, + PgpSignature.DefaultCertification + }; + + private long keyId; private byte[] fingerprint; private int keyStrength; - internal PublicKeyPacket publicPk; + internal PublicKeyPacket publicPk; internal TrustPacket trustPk; internal IList keySigs = Platform.CreateArrayList(); internal IList ids = Platform.CreateArrayList(); @@ -34,45 +34,45 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp internal IList idSigs = Platform.CreateArrayList(); internal IList subSigs; - private void Init() + private void Init() { IBcpgKey key = publicPk.Key; - if (publicPk.Version <= 3) + if (publicPk.Version <= 3) { RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key; - this.keyId = rK.Modulus.LongValue; + this.keyId = rK.Modulus.LongValue; - try + try { IDigest digest = DigestUtilities.GetDigest("MD5"); - byte[] bytes = rK.Modulus.ToByteArrayUnsigned(); - digest.BlockUpdate(bytes, 0, bytes.Length); + byte[] bytes = rK.Modulus.ToByteArrayUnsigned(); + digest.BlockUpdate(bytes, 0, bytes.Length); - bytes = rK.PublicExponent.ToByteArrayUnsigned(); - digest.BlockUpdate(bytes, 0, bytes.Length); + bytes = rK.PublicExponent.ToByteArrayUnsigned(); + digest.BlockUpdate(bytes, 0, bytes.Length); - this.fingerprint = DigestUtilities.DoFinal(digest); + this.fingerprint = DigestUtilities.DoFinal(digest); } - //catch (NoSuchAlgorithmException) - catch (Exception e) + //catch (NoSuchAlgorithmException) + catch (Exception e) { throw new IOException("can't find MD5", e); } - this.keyStrength = rK.Modulus.BitLength; + this.keyStrength = rK.Modulus.BitLength; } else { byte[] kBytes = publicPk.GetEncodedContents(); - try + try { IDigest digest = DigestUtilities.GetDigest("SHA1"); - digest.Update(0x99); + digest.Update(0x99); digest.Update((byte)(kBytes.Length >> 8)); digest.Update((byte)kBytes.Length); digest.BlockUpdate(kBytes, 0, kBytes.Length); @@ -83,7 +83,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new IOException("can't find SHA1", e); } - this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56) + this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56) | ((ulong)fingerprint[fingerprint.Length - 7] << 48) | ((ulong)fingerprint[fingerprint.Length - 6] << 40) | ((ulong)fingerprint[fingerprint.Length - 5] << 32) @@ -92,7 +92,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp | ((ulong)fingerprint[fingerprint.Length - 2] << 8) | (ulong)fingerprint[fingerprint.Length - 1]); - if (key is RsaPublicBcpgKey) + if (key is RsaPublicBcpgKey) { this.keyStrength = ((RsaPublicBcpgKey)key).Modulus.BitLength; } @@ -107,57 +107,57 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - /// <summary> - /// Create a PgpPublicKey from the passed in lightweight one. - /// </summary> - /// <remarks> - /// Note: the time passed in affects the value of the key's keyId, so you probably only want - /// to do this once for a lightweight key, or make sure you keep track of the time you used. - /// </remarks> - /// <param name="algorithm">Asymmetric algorithm type representing the public key.</param> - /// <param name="pubKey">Actual public key to associate.</param> - /// <param name="time">Date of creation.</param> - /// <exception cref="ArgumentException">If <c>pubKey</c> is not public.</exception> - /// <exception cref="PgpException">On key creation problem.</exception> + /// <summary> + /// Create a PgpPublicKey from the passed in lightweight one. + /// </summary> + /// <remarks> + /// Note: the time passed in affects the value of the key's keyId, so you probably only want + /// to do this once for a lightweight key, or make sure you keep track of the time you used. + /// </remarks> + /// <param name="algorithm">Asymmetric algorithm type representing the public key.</param> + /// <param name="pubKey">Actual public key to associate.</param> + /// <param name="time">Date of creation.</param> + /// <exception cref="ArgumentException">If <c>pubKey</c> is not public.</exception> + /// <exception cref="PgpException">On key creation problem.</exception> public PgpPublicKey( PublicKeyAlgorithmTag algorithm, AsymmetricKeyParameter pubKey, DateTime time) { - if (pubKey.IsPrivate) - throw new ArgumentException("Expected a public key", "pubKey"); + if (pubKey.IsPrivate) + throw new ArgumentException("Expected a public key", "pubKey"); - IBcpgKey bcpgKey; + IBcpgKey bcpgKey; if (pubKey is RsaKeyParameters) { RsaKeyParameters rK = (RsaKeyParameters) pubKey; - bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent); + bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent); } else if (pubKey is DsaPublicKeyParameters) { DsaPublicKeyParameters dK = (DsaPublicKeyParameters) pubKey; DsaParameters dP = dK.Parameters; - bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y); + bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y); } else if (pubKey is ElGamalPublicKeyParameters) { ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey; ElGamalParameters eS = eK.Parameters; - bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y); + bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y); } else { throw new PgpException("unknown key class"); } - this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey); + this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey); this.ids = Platform.CreateArrayList(); this.idSigs = Platform.CreateArrayList(); - try + try { Init(); } @@ -167,7 +167,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - /// <summary>Constructor for a sub-key.</summary> + /// <summary>Constructor for a sub-key.</summary> internal PgpPublicKey( PublicKeyPacket publicPk, TrustPacket trustPk, @@ -177,10 +177,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.trustPk = trustPk; this.subSigs = sigs; - Init(); + Init(); } - internal PgpPublicKey( + internal PgpPublicKey( PgpPublicKey key, TrustPacket trust, IList subSigs) @@ -189,19 +189,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.trustPk = trust; this.subSigs = subSigs; - this.fingerprint = key.fingerprint; + this.fingerprint = key.fingerprint; this.keyId = key.keyId; this.keyStrength = key.keyStrength; } - /// <summary>Copy constructor.</summary> - /// <param name="pubKey">The public key to copy.</param> + /// <summary>Copy constructor.</summary> + /// <param name="pubKey">The public key to copy.</param> internal PgpPublicKey( PgpPublicKey pubKey) { this.publicPk = pubKey.publicPk; - this.keySigs = Platform.CreateArrayList(pubKey.keySigs); + this.keySigs = Platform.CreateArrayList(pubKey.keySigs); this.ids = Platform.CreateArrayList(pubKey.ids); this.idTrusts = Platform.CreateArrayList(pubKey.idTrusts); this.idSigs = Platform.CreateArrayList(pubKey.idSigs.Count); @@ -210,7 +210,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.idSigs.Add(Platform.CreateArrayList((IList)pubKey.idSigs[i])); } - if (pubKey.subSigs != null) + if (pubKey.subSigs != null) { this.subSigs = Platform.CreateArrayList(pubKey.subSigs.Count); for (int i = 0; i != pubKey.subSigs.Count; i++) @@ -219,12 +219,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - this.fingerprint = pubKey.fingerprint; + this.fingerprint = pubKey.fingerprint; this.keyId = pubKey.keyId; this.keyStrength = pubKey.keyStrength; } - internal PgpPublicKey( + internal PgpPublicKey( PublicKeyPacket publicPk, TrustPacket trustPk, IList keySigs, @@ -239,10 +239,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.idTrusts = idTrusts; this.idSigs = idSigs; - Init(); + Init(); } - internal PgpPublicKey( + internal PgpPublicKey( PublicKeyPacket publicPk, IList ids, IList idSigs) @@ -253,159 +253,160 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp Init(); } - /// <summary>The version of this key.</summary> + /// <summary>The version of this key.</summary> public int Version { - get { return publicPk.Version; } + get { return publicPk.Version; } } - /// <summary>The creation time of this key.</summary> - public DateTime CreationTime + /// <summary>The creation time of this key.</summary> + public DateTime CreationTime { - get { return publicPk.GetTime(); } + get { return publicPk.GetTime(); } } - /// <summary>The number of valid days from creation time - zero means no expiry.</summary> + /// <summary>The number of valid days from creation time - zero means no expiry.</summary> public int ValidDays { - get - { - if (publicPk.Version > 3) - { - return (int)(GetValidSeconds() / (24 * 60 * 60)); - } - - return publicPk.ValidDays; - } - } - - /// <summary>Return the trust data associated with the public key, if present.</summary> - /// <returns>A byte array with trust data, null otherwise.</returns> - public byte[] GetTrustData() - { - if (trustPk == null) - { - return null; - } - - return trustPk.GetLevelAndTrustAmount(); - } - - /// <summary>The number of valid seconds from creation time - zero means no expiry.</summary> - public long GetValidSeconds() - { - if (publicPk.Version > 3) - { - if (IsMasterKey) - { - for (int i = 0; i != MasterKeyCertificationTypes.Length; i++) - { - long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]); - - if (seconds >= 0) - { - return seconds; - } - } - } - else - { - long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding); - - if (seconds >= 0) - { - return seconds; - } - } - - return 0; - } - - return (long) publicPk.ValidDays * 24 * 60 * 60; - } - - private long GetExpirationTimeFromSig( - bool selfSigned, - int signatureType) - { - foreach (PgpSignature sig in GetSignaturesOfType(signatureType)) - { - if (!selfSigned || sig.KeyId == KeyId) - { - PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets(); - - if (hashed != null) - { - return hashed.GetKeyExpirationTime(); - } - - return 0; - } - } - - return -1; - } - - /// <summary>The keyId associated with the public key.</summary> + get + { + if (publicPk.Version > 3) + { + return (int)(GetValidSeconds() / (24 * 60 * 60)); + } + + return publicPk.ValidDays; + } + } + + /// <summary>Return the trust data associated with the public key, if present.</summary> + /// <returns>A byte array with trust data, null otherwise.</returns> + public byte[] GetTrustData() + { + if (trustPk == null) + { + return null; + } + + return Arrays.Clone(trustPk.GetLevelAndTrustAmount()); + } + + /// <summary>The number of valid seconds from creation time - zero means no expiry.</summary> + public long GetValidSeconds() + { + if (publicPk.Version > 3) + { + if (IsMasterKey) + { + for (int i = 0; i != MasterKeyCertificationTypes.Length; i++) + { + long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]); + + if (seconds >= 0) + { + return seconds; + } + } + } + else + { + long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding); + + if (seconds >= 0) + { + return seconds; + } + } + + return 0; + } + + return (long) publicPk.ValidDays * 24 * 60 * 60; + } + + private long GetExpirationTimeFromSig( + bool selfSigned, + int signatureType) + { + foreach (PgpSignature sig in GetSignaturesOfType(signatureType)) + { + if (!selfSigned || sig.KeyId == KeyId) + { + PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets(); + + if (hashed != null) + { + return hashed.GetKeyExpirationTime(); + } + + return 0; + } + } + + return -1; + } + + /// <summary>The keyId associated with the public key.</summary> public long KeyId { get { return keyId; } } - /// <summary>The fingerprint of the key</summary> + /// <summary>The fingerprint of the key</summary> public byte[] GetFingerprint() { - return (byte[]) fingerprint.Clone(); + return (byte[]) fingerprint.Clone(); } - /// <summary> - /// Check if this key has an algorithm type that makes it suitable to use for encryption. - /// </summary> - /// <remarks> - /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for - /// determining the preferred use of the key. - /// </remarks> - /// <returns> - /// <c>true</c> if this key algorithm is suitable for encryption. - /// </returns> - public bool IsEncryptionKey + /// <summary> + /// Check if this key has an algorithm type that makes it suitable to use for encryption. + /// </summary> + /// <remarks> + /// Note: with version 4 keys KeyFlags subpackets should also be considered when present for + /// determining the preferred use of the key. + /// </remarks> + /// <returns> + /// <c>true</c> if this key algorithm is suitable for encryption. + /// </returns> + public bool IsEncryptionKey { get { - switch (publicPk.Algorithm) - { - case PublicKeyAlgorithmTag.ElGamalEncrypt: - case PublicKeyAlgorithmTag.ElGamalGeneral: - case PublicKeyAlgorithmTag.RsaEncrypt: - case PublicKeyAlgorithmTag.RsaGeneral: - return true; - default: - return false; - } + switch (publicPk.Algorithm) + { + case PublicKeyAlgorithmTag.ECDH: + case PublicKeyAlgorithmTag.ElGamalEncrypt: + case PublicKeyAlgorithmTag.ElGamalGeneral: + case PublicKeyAlgorithmTag.RsaEncrypt: + case PublicKeyAlgorithmTag.RsaGeneral: + return true; + default: + return false; + } } } - /// <summary>True, if this is a master key.</summary> + /// <summary>True, if this is a master key.</summary> public bool IsMasterKey { get { return subSigs == null; } } - /// <summary>The algorithm code associated with the public key.</summary> + /// <summary>The algorithm code associated with the public key.</summary> public PublicKeyAlgorithmTag Algorithm { - get { return publicPk.Algorithm; } + get { return publicPk.Algorithm; } } - /// <summary>The strength of the key in bits.</summary> + /// <summary>The strength of the key in bits.</summary> public int BitStrength { get { return keyStrength; } } - /// <summary>The public key contained in the object.</summary> - /// <returns>A lightweight public key.</returns> - /// <exception cref="PgpException">If the key algorithm is not recognised.</exception> + /// <summary>The public key contained in the object.</summary> + /// <returns>A lightweight public key.</returns> + /// <exception cref="PgpException">If the key algorithm is not recognised.</exception> public AsymmetricKeyParameter GetKey() { try @@ -438,50 +439,50 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - /// <summary>Allows enumeration of any user IDs associated with the key.</summary> - /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns> + /// <summary>Allows enumeration of any user IDs associated with the key.</summary> + /// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns> public IEnumerable GetUserIds() { IList temp = Platform.CreateArrayList(); - foreach (object o in ids) - { - if (o is string) - { - temp.Add(o); + foreach (object o in ids) + { + if (o is string) + { + temp.Add(o); } } - return new EnumerableProxy(temp); + return new EnumerableProxy(temp); } - /// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary> - /// <returns>An <c>IEnumerable</c> of <c>PgpUserAttributeSubpacketVector</c> objects.</returns> + /// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary> + /// <returns>An <c>IEnumerable</c> of <c>PgpUserAttributeSubpacketVector</c> objects.</returns> public IEnumerable GetUserAttributes() { IList temp = Platform.CreateArrayList(); - foreach (object o in ids) - { - if (o is PgpUserAttributeSubpacketVector) - { - temp.Add(o); - } - } + foreach (object o in ids) + { + if (o is PgpUserAttributeSubpacketVector) + { + temp.Add(o); + } + } - return new EnumerableProxy(temp); + return new EnumerableProxy(temp); } - /// <summary>Allows enumeration of any signatures associated with the passed in id.</summary> - /// <param name="id">The ID to be matched.</param> - /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns> + /// <summary>Allows enumeration of any signatures associated with the passed in id.</summary> + /// <param name="id">The ID to be matched.</param> + /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns> public IEnumerable GetSignaturesForId( string id) { - if (id == null) - throw new ArgumentNullException("id"); + if (id == null) + throw new ArgumentNullException("id"); - for (int i = 0; i != ids.Count; i++) + for (int i = 0; i != ids.Count; i++) { if (id.Equals(ids[i])) { @@ -489,12 +490,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - return null; + return null; } - /// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary> - /// <param name="userAttributes">The vector of user attributes to be matched.</param> - /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns> + /// <summary>Allows enumeration of signatures associated with the passed in user attributes.</summary> + /// <param name="userAttributes">The vector of user attributes to be matched.</param> + /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns> public IEnumerable GetSignaturesForUserAttribute( PgpUserAttributeSubpacketVector userAttributes) { @@ -506,18 +507,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - return null; + return null; } - /// <summary>Allows enumeration of signatures of the passed in type that are on this key.</summary> - /// <param name="signatureType">The type of the signature to be returned.</param> - /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns> + /// <summary>Allows enumeration of signatures of the passed in type that are on this key.</summary> + /// <param name="signatureType">The type of the signature to be returned.</param> + /// <returns>An <c>IEnumerable</c> of <c>PgpSignature</c> objects.</returns> public IEnumerable GetSignaturesOfType( int signatureType) { IList temp = Platform.CreateArrayList(); - foreach (PgpSignature sig in GetSignatures()) + foreach (PgpSignature sig in GetSignatures()) { if (sig.SignatureType == signatureType) { @@ -525,63 +526,79 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - return new EnumerableProxy(temp); + return new EnumerableProxy(temp); } - /// <summary>Allows enumeration of all signatures/certifications associated with this key.</summary> - /// <returns>An <c>IEnumerable</c> with all signatures/certifications.</returns> + /// <summary>Allows enumeration of all signatures/certifications associated with this key.</summary> + /// <returns>An <c>IEnumerable</c> with all signatures/certifications.</returns> public IEnumerable GetSignatures() { - IList sigs; - if (subSigs != null) - { - sigs = subSigs; - } - else - { + IList sigs = subSigs; + if (sigs == null) + { sigs = Platform.CreateArrayList(keySigs); - foreach (ICollection extraSigs in idSigs) - { + foreach (ICollection extraSigs in idSigs) + { CollectionUtilities.AddRange(sigs, extraSigs); - } - } + } + } + + return new EnumerableProxy(sigs); + } + + /** + * Return all signatures/certifications directly associated with this key (ie, not to a user id). + * + * @return an iterator (possibly empty) with all signatures/certifications. + */ + public IEnumerable GetKeySignatures() + { + IList sigs = subSigs; + if (sigs == null) + { + sigs = Platform.CreateArrayList(keySigs); + } + return new EnumerableProxy(sigs); + } - return new EnumerableProxy(sigs); + public PublicKeyPacket PublicKeyPacket + { + get { return publicPk; } } - public byte[] GetEncoded() + public byte[] GetEncoded() { MemoryStream bOut = new MemoryStream(); Encode(bOut); return bOut.ToArray(); } - public void Encode( + public void Encode( Stream outStr) { BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); - bcpgOut.WritePacket(publicPk); + bcpgOut.WritePacket(publicPk); if (trustPk != null) { bcpgOut.WritePacket(trustPk); } - if (subSigs == null) // not a sub-key + if (subSigs == null) // not a sub-key { - foreach (PgpSignature keySig in keySigs) - { - keySig.Encode(bcpgOut); - } + foreach (PgpSignature keySig in keySigs) + { + keySig.Encode(bcpgOut); + } - for (int i = 0; i != ids.Count; i++) + for (int i = 0; i != ids.Count; i++) { if (ids[i] is string) { string id = (string) ids[i]; - bcpgOut.WritePacket(new UserIdPacket(id)); + bcpgOut.WritePacket(new UserIdPacket(id)); } else { @@ -589,28 +606,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray())); } - if (idTrusts[i] != null) + if (idTrusts[i] != null) { bcpgOut.WritePacket((ContainedPacket)idTrusts[i]); } - foreach (PgpSignature sig in (IList) idSigs[i]) - { - sig.Encode(bcpgOut); - } + foreach (PgpSignature sig in (IList) idSigs[i]) + { + sig.Encode(bcpgOut); + } } } else { - foreach (PgpSignature subSig in subSigs) - { - subSig.Encode(bcpgOut); - } + foreach (PgpSignature subSig in subSigs) + { + subSig.Encode(bcpgOut); + } } } - /// <summary>Check whether this (sub)key has a revocation signature on it.</summary> - /// <returns>True, if this (sub)key has been revoked.</returns> + /// <summary>Check whether this (sub)key has a revocation signature on it.</summary> + /// <returns>True, if this (sub)key has been revoked.</returns> public bool IsRevoked() { int ns = 0; @@ -638,98 +655,98 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return revoked; } - /// <summary>Add a certification for an id to the given public key.</summary> - /// <param name="key">The key the certification is to be added to.</param> - /// <param name="id">The ID the certification is associated with.</param> - /// <param name="certification">The new certification.</param> - /// <returns>The re-certified key.</returns> + /// <summary>Add a certification for an id to the given public key.</summary> + /// <param name="key">The key the certification is to be added to.</param> + /// <param name="id">The ID the certification is associated with.</param> + /// <param name="certification">The new certification.</param> + /// <returns>The re-certified key.</returns> public static PgpPublicKey AddCertification( PgpPublicKey key, string id, PgpSignature certification) { - return AddCert(key, id, certification); - } - - /// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary> - /// <param name="key">The key the certification is to be added to.</param> - /// <param name="userAttributes">The attributes the certification is associated with.</param> - /// <param name="certification">The new certification.</param> - /// <returns>The re-certified key.</returns> - public static PgpPublicKey AddCertification( - PgpPublicKey key, - PgpUserAttributeSubpacketVector userAttributes, - PgpSignature certification) - { - return AddCert(key, userAttributes, certification); - } - - private static PgpPublicKey AddCert( - PgpPublicKey key, - object id, - PgpSignature certification) - { - PgpPublicKey returnKey = new PgpPublicKey(key); - IList sigList = null; - - for (int i = 0; i != returnKey.ids.Count; i++) - { - if (id.Equals(returnKey.ids[i])) - { - sigList = (IList) returnKey.idSigs[i]; - } - } - - if (sigList != null) - { - sigList.Add(certification); - } - else - { - sigList = Platform.CreateArrayList(); - sigList.Add(certification); - returnKey.ids.Add(id); - returnKey.idTrusts.Add(null); - returnKey.idSigs.Add(sigList); - } - - return returnKey; - } - - /// <summary> - /// Remove any certifications associated with a user attribute subpacket on a key. - /// </summary> - /// <param name="key">The key the certifications are to be removed from.</param> - /// <param name="userAttributes">The attributes to be removed.</param> - /// <returns> - /// The re-certified key, or null if the user attribute subpacket was not found on the key. - /// </returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - PgpUserAttributeSubpacketVector userAttributes) - { - return RemoveCert(key, userAttributes); - } - - /// <summary>Remove any certifications associated with a given ID on a key.</summary> - /// <param name="key">The key the certifications are to be removed from.</param> - /// <param name="id">The ID that is to be removed.</param> - /// <returns>The re-certified key, or null if the ID was not found on the key.</returns> + return AddCert(key, id, certification); + } + + /// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary> + /// <param name="key">The key the certification is to be added to.</param> + /// <param name="userAttributes">The attributes the certification is associated with.</param> + /// <param name="certification">The new certification.</param> + /// <returns>The re-certified key.</returns> + public static PgpPublicKey AddCertification( + PgpPublicKey key, + PgpUserAttributeSubpacketVector userAttributes, + PgpSignature certification) + { + return AddCert(key, userAttributes, certification); + } + + private static PgpPublicKey AddCert( + PgpPublicKey key, + object id, + PgpSignature certification) + { + PgpPublicKey returnKey = new PgpPublicKey(key); + IList sigList = null; + + for (int i = 0; i != returnKey.ids.Count; i++) + { + if (id.Equals(returnKey.ids[i])) + { + sigList = (IList) returnKey.idSigs[i]; + } + } + + if (sigList != null) + { + sigList.Add(certification); + } + else + { + sigList = Platform.CreateArrayList(); + sigList.Add(certification); + returnKey.ids.Add(id); + returnKey.idTrusts.Add(null); + returnKey.idSigs.Add(sigList); + } + + return returnKey; + } + + /// <summary> + /// Remove any certifications associated with a user attribute subpacket on a key. + /// </summary> + /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="userAttributes">The attributes to be removed.</param> + /// <returns> + /// The re-certified key, or null if the user attribute subpacket was not found on the key. + /// </returns> + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + PgpUserAttributeSubpacketVector userAttributes) + { + return RemoveCert(key, userAttributes); + } + + /// <summary>Remove any certifications associated with a given ID on a key.</summary> + /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="id">The ID that is to be removed.</param> + /// <returns>The re-certified key, or null if the ID was not found on the key.</returns> public static PgpPublicKey RemoveCertification( PgpPublicKey key, string id) { - return RemoveCert(key, id); - } + return RemoveCert(key, id); + } - private static PgpPublicKey RemoveCert( - PgpPublicKey key, - object id) - { - PgpPublicKey returnKey = new PgpPublicKey(key); + private static PgpPublicKey RemoveCert( + PgpPublicKey key, + object id) + { + PgpPublicKey returnKey = new PgpPublicKey(key); bool found = false; - for (int i = 0; i < returnKey.ids.Count; i++) + for (int i = 0; i < returnKey.ids.Count; i++) { if (id.Equals(returnKey.ids[i])) { @@ -740,64 +757,64 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - return found ? returnKey : null; + return found ? returnKey : null; } - /// <summary>Remove a certification associated with a given ID on a key.</summary> - /// <param name="key">The key the certifications are to be removed from.</param> - /// <param name="id">The ID that the certfication is to be removed from.</param> - /// <param name="certification">The certfication to be removed.</param> - /// <returns>The re-certified key, or null if the certification was not found.</returns> + /// <summary>Remove a certification associated with a given ID on a key.</summary> + /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="id">The ID that the certfication is to be removed from.</param> + /// <param name="certification">The certfication to be removed.</param> + /// <returns>The re-certified key, or null if the certification was not found.</returns> public static PgpPublicKey RemoveCertification( PgpPublicKey key, string id, PgpSignature certification) { - return RemoveCert(key, id, certification); - } - - /// <summary>Remove a certification associated with a given user attributes on a key.</summary> - /// <param name="key">The key the certifications are to be removed from.</param> - /// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param> - /// <param name="certification">The certification to be removed.</param> - /// <returns>The re-certified key, or null if the certification was not found.</returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - PgpUserAttributeSubpacketVector userAttributes, - PgpSignature certification) - { - return RemoveCert(key, userAttributes, certification); - } - - private static PgpPublicKey RemoveCert( - PgpPublicKey key, - object id, - PgpSignature certification) - { - PgpPublicKey returnKey = new PgpPublicKey(key); + return RemoveCert(key, id, certification); + } + + /// <summary>Remove a certification associated with a given user attributes on a key.</summary> + /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param> + /// <param name="certification">The certification to be removed.</param> + /// <returns>The re-certified key, or null if the certification was not found.</returns> + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + PgpUserAttributeSubpacketVector userAttributes, + PgpSignature certification) + { + return RemoveCert(key, userAttributes, certification); + } + + private static PgpPublicKey RemoveCert( + PgpPublicKey key, + object id, + PgpSignature certification) + { + PgpPublicKey returnKey = new PgpPublicKey(key); bool found = false; - for (int i = 0; i < returnKey.ids.Count; i++) + for (int i = 0; i < returnKey.ids.Count; i++) { if (id.Equals(returnKey.ids[i])) { IList certs = (IList) returnKey.idSigs[i]; found = certs.Contains(certification); - if (found) - { - certs.Remove(certification); - } + if (found) + { + certs.Remove(certification); + } } } - return found ? returnKey : null; + return found ? returnKey : null; } - /// <summary>Add a revocation or some other key certification to a key.</summary> - /// <param name="key">The key the revocation is to be added to.</param> - /// <param name="certification">The key signature to be added.</param> - /// <returns>The new changed public key object.</returns> + /// <summary>Add a revocation or some other key certification to a key.</summary> + /// <param name="key">The key the revocation is to be added to.</param> + /// <param name="certification">The key signature to be added.</param> + /// <returns>The new changed public key object.</returns> public static PgpPublicKey AddCertification( PgpPublicKey key, PgpSignature certification) @@ -817,9 +834,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } - PgpPublicKey returnKey = new PgpPublicKey(key); + PgpPublicKey returnKey = new PgpPublicKey(key); - if (returnKey.subSigs != null) + if (returnKey.subSigs != null) { returnKey.subSigs.Add(certification); } @@ -828,63 +845,63 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp returnKey.keySigs.Add(certification); } - return returnKey; + return returnKey; } - /// <summary>Remove a certification from the key.</summary> - /// <param name="key">The key the certifications are to be removed from.</param> - /// <param name="certification">The certfication to be removed.</param> - /// <returns>The modified key, null if the certification was not found.</returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - PgpSignature certification) - { - PgpPublicKey returnKey = new PgpPublicKey(key); - IList sigs = returnKey.subSigs != null - ? returnKey.subSigs - : returnKey.keySigs; + /// <summary>Remove a certification from the key.</summary> + /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="certification">The certfication to be removed.</param> + /// <returns>The modified key, null if the certification was not found.</returns> + public static PgpPublicKey RemoveCertification( + PgpPublicKey key, + PgpSignature certification) + { + PgpPublicKey returnKey = new PgpPublicKey(key); + IList sigs = returnKey.subSigs != null + ? returnKey.subSigs + : returnKey.keySigs; // bool found = sigs.Remove(certification); - int pos = sigs.IndexOf(certification); - bool found = pos >= 0; - - if (found) - { - sigs.RemoveAt(pos); - } - else - { - foreach (String id in key.GetUserIds()) - { - foreach (object sig in key.GetSignaturesForId(id)) - { - // TODO Is this the right type of equality test? - if (certification == sig) - { - found = true; - returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); - } - } - } - - if (!found) - { - foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) - { - foreach (object sig in key.GetSignaturesForUserAttribute(id)) - { - // TODO Is this the right type of equality test? - if (certification == sig) - { - found = true; - returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); - } - } - } - } - } - - return returnKey; - } - } + int pos = sigs.IndexOf(certification); + bool found = pos >= 0; + + if (found) + { + sigs.RemoveAt(pos); + } + else + { + foreach (String id in key.GetUserIds()) + { + foreach (object sig in key.GetSignaturesForId(id)) + { + // TODO Is this the right type of equality test? + if (certification == sig) + { + found = true; + returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); + } + } + } + + if (!found) + { + foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) + { + foreach (object sig in key.GetSignaturesForUserAttribute(id)) + { + // TODO Is this the right type of equality test? + if (certification == sig) + { + found = true; + returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification); + } + } + } + } + } + + return returnKey; + } + } } diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs index 0cf113f65..c1aea50d6 100644 --- a/crypto/src/security/SignerUtilities.cs +++ b/crypto/src/security/SignerUtilities.cs @@ -261,10 +261,10 @@ namespace Org.BouncyCastle.Security } /// <summary> - /// Returns a ObjectIdentifier for a give encoding. + /// Returns an ObjectIdentifier for a given encoding. /// </summary> /// <param name="mechanism">A string representation of the encoding.</param> - /// <returns>A DerObjectIdentifier, null if the Oid is not available.</returns> + /// <returns>A DerObjectIdentifier, null if the OID is not available.</returns> // TODO Don't really want to support this public static DerObjectIdentifier GetObjectIdentifier( string mechanism) diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs index 27fd18d6d..8614baead 100644 --- a/crypto/src/util/Arrays.cs +++ b/crypto/src/util/Arrays.cs @@ -339,6 +339,11 @@ namespace Org.BouncyCastle.Utilities return data == null ? null : (int[])data.Clone(); } + internal static uint[] Clone(uint[] data) + { + return data == null ? null : (uint[])data.Clone(); + } + public static long[] Clone(long[] data) { return data == null ? null : (long[])data.Clone(); |