using KFDtool.P25.Constant; using KFDtool.Shared; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace KFDtool.P25.Validator { public class FieldValidator { public static bool IsValidKeysetId(int keysetId) { /* TIA 102.AACA-A 10.3.11 */ if (keysetId < 0x01 || keysetId > 0xFF) { return false; } else { return true; } } public static bool IsValidSln(int sln) { /* TIA 102.AACA-A 10.3.25 */ if (sln < 0 || sln > 65535) { return false; } else { return true; } } public static bool IsValidKeyId(int keyId) { /* TIA 102.AACA-A 10.3.10 */ if (keyId < 0x0000 || keyId > 0xFFFF) { return false; } else { return true; } } public static bool IsValidAlgorithmId(int algId) { /* TIA-102.BAAC-D 2.8 */ if (algId < 0x00 || algId > 0xFF) { return false; } else { return true; } } public static bool IsValidSingleDesKeyParity(List key) { if (key.Count != 8) { throw new ArgumentOutOfRangeException(); } bool result = true; foreach (byte b in key) { bool set = Convert.ToBoolean(b & 0x01); // least significant bit is the parity bit // from .NET 4.8 refsrc system\security\cryptography\utils.cs FixupKeyParity() byte c = (byte)(b & 0xfe); byte tmp1 = (byte)((c & 0xF) ^ (c >> 4)); byte tmp2 = (byte)((tmp1 & 0x3) ^ (tmp1 >> 2)); byte sumBitsMod2 = (byte)((tmp2 & 0x1) ^ (tmp2 >> 1)); bool calc = false; if (sumBitsMod2 == 0) { calc = true; } if (set != calc) // parity bit is incorrect { return false; } } return result; } public static Tuple KeyloadValidate(int keysetId, int sln, bool isKek, int keyId, int algId, List key) { if (!IsValidKeysetId(keysetId)) { return Tuple.Create(ValidateResult.Error, "Keyset ID invalid - valid range 1 to 255 (dec), 0x01 to 0xFF (hex)"); } if (!IsValidSln(sln)) { return Tuple.Create(ValidateResult.Error, "SLN invalid - valid range 0 to 65535 (dec), 0x0000 to 0xFFFF (hex)"); } if (!IsValidKeyId(keyId)) { return Tuple.Create(ValidateResult.Error, "Key ID invalid - valid range 0 to 65535 (dec), 0x0000 to 0xFFFF (hex)"); } if (!IsValidAlgorithmId(algId)) { return Tuple.Create(ValidateResult.Error, "Algorithm ID invalid - valid range 0 to 255 (dec), 0x00 to 0xFF (hex)"); } if (algId == (byte)AlgorithmId.CLEAR) { return Tuple.Create(ValidateResult.Error, "Algorithm ID 0x80 is reserved for clear operation"); } else if (algId == (byte)AlgorithmId.ACCORDION || algId == (byte)AlgorithmId.BATON_ODD || algId == (byte)AlgorithmId.FIREFLY || algId == (byte)AlgorithmId.MAYFLY || algId == (byte)AlgorithmId.SAVILLE || algId == (byte)AlgorithmId.PADSTONE || algId == (byte)AlgorithmId.BATON_EVEN) { return Tuple.Create(ValidateResult.Warning, string.Format("Algorithm ID 0x{0:X2} is a Type 1 algorithm - no key validation has been performed", algId)); } else if (algId == (byte)AlgorithmId.DESOFB || algId == (byte)AlgorithmId.DESXL) { if (key.Count != 8) { return Tuple.Create(ValidateResult.Error, string.Format("Key length invalid - expected 8 bytes, got {0} bytes", key.Count)); } if (!IsValidSingleDesKeyParity(key)) { return Tuple.Create(ValidateResult.Error, "Key parity invalid"); } // des weak keys per NIST SP 800-67 Rev 2 3.3.2 List> weakKeys = new List> { // des weak keys (4) new List { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, new List { 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE }, new List { 0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1 }, new List { 0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E }, // des semi weak keys (12) new List { 0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E }, new List { 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01 }, new List { 0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1 }, new List { 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01 }, new List { 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE }, new List { 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01 }, new List { 0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1 }, new List { 0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E }, new List { 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE }, new List { 0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E }, new List { 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE }, new List { 0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1 }, // des possibly weak keys (48) new List { 0x01, 0x01, 0x1F, 0x1F, 0x01, 0x01, 0x0E, 0x0E }, new List { 0x01, 0x01, 0xE0, 0xE0, 0x01, 0x01, 0xF1, 0xF1 }, new List { 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE }, new List { 0x01, 0x1F, 0x1F, 0x01, 0x01, 0x0E, 0x0E, 0x01 }, new List { 0x01, 0x1F, 0xE0, 0xFE, 0x01, 0x0E, 0xF1, 0xFE }, new List { 0x01, 0x1F, 0xFE, 0xE0, 0x01, 0x0E, 0xFE, 0xF1 }, new List { 0x01, 0xE0, 0x1F, 0xFE, 0x01, 0xF1, 0x0E, 0xFE }, new List { 0xFE, 0x01, 0xE0, 0x1F, 0xFE, 0x01, 0xF1, 0x0E }, new List { 0x01, 0xE0, 0xE0, 0x01, 0x01, 0xF1, 0xF1, 0x01 }, new List { 0x01, 0xE0, 0xFE, 0x1F, 0x01, 0xF1, 0xFE, 0x0E }, new List { 0x01, 0xFE, 0x1F, 0xE0, 0x01, 0xFE, 0x0E, 0xF1 }, new List { 0x01, 0xFE, 0xE0, 0x1F, 0x01, 0xFE, 0xF1, 0x0E }, new List { 0x01, 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE, 0x01 }, new List { 0x1F, 0x01, 0x01, 0x1F, 0x0E, 0x01, 0x01, 0x0E }, new List { 0x1F, 0x01, 0xE0, 0xFE, 0x0E, 0x01, 0xF1, 0xFE }, new List { 0x1F, 0x01, 0xFE, 0xE0, 0x0E, 0x01, 0xFE, 0xF1 }, new List { 0x1F, 0x1F, 0x01, 0x01, 0x0E, 0x0E, 0x01, 0x01 }, new List { 0x1F, 0x1F, 0xE0, 0xE0, 0x0E, 0x0E, 0xF1, 0xF1 }, new List { 0x1F, 0x1F, 0xFE, 0xFE, 0x0E, 0x0E, 0xFE, 0xFE }, new List { 0x1F, 0xE0, 0x01, 0xFE, 0x0E, 0xF1, 0x01, 0xFE }, new List { 0x1F, 0xE0, 0xE0, 0x1F, 0x0E, 0xF1, 0xF1, 0x0E }, new List { 0x1F, 0xE0, 0xFE, 0x01, 0x0E, 0xF1, 0xFE, 0x01 }, new List { 0x1F, 0xFE, 0x01, 0xE0, 0x0E, 0xFE, 0x01, 0xF1 }, new List { 0x1F, 0xFE, 0xE0, 0x01, 0x0E, 0xFE, 0xF1, 0x01 }, new List { 0x1F, 0xFE, 0xFE, 0x1F, 0x0E, 0xFE, 0xFE, 0x0E }, new List { 0x1F, 0xFE, 0xFE, 0x1F, 0x0E, 0xFE, 0xFE, 0x0E }, new List { 0xE0, 0x01, 0x1F, 0xFE, 0xF1, 0x01, 0x0E, 0xFE }, new List { 0xE0, 0x01, 0xFE, 0x1F, 0xF1, 0x01, 0xFE, 0x0E }, new List { 0xE0, 0x1F, 0x01, 0xFE, 0xF1, 0x0E, 0x01, 0xFE }, new List { 0xE0, 0x1F, 0x1F, 0xE0, 0xF1, 0x0E, 0x0E, 0xF1 }, new List { 0xE0, 0x1F, 0xFE, 0x01, 0xF1, 0x0E, 0xFE, 0x01 }, new List { 0xE0, 0xE0, 0x01, 0x01, 0xF1, 0xF1, 0x01, 0x01 }, new List { 0xE0, 0xE0, 0x1F, 0x1F, 0xF1, 0xF1, 0x0E, 0x0E }, new List { 0xE0, 0xE0, 0xFE, 0xFE, 0xF1, 0xF1, 0xFE, 0xFE }, new List { 0xE0, 0xFE, 0x01, 0x1F, 0xF1, 0xFE, 0x01, 0x0E }, new List { 0xE0, 0xFE, 0x1F, 0x01, 0xF1, 0xFE, 0x0E, 0x01 }, new List { 0xE0, 0xFE, 0xFE, 0xE0, 0xF1, 0xFE, 0xFE, 0xF1 }, new List { 0xFE, 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01, 0xFE }, new List { 0xFE, 0x01, 0x1F, 0xE0, 0xFE, 0x01, 0x0E, 0xF1 }, new List { 0xFE, 0x1F, 0x01, 0xE0, 0xFE, 0x0E, 0x01, 0xF1 }, new List { 0xFE, 0x1F, 0xE0, 0x01, 0xFE, 0x0E, 0xF1, 0x01 }, new List { 0xFE, 0x1F, 0x1F, 0xFE, 0xFE, 0x0E, 0x0E, 0xFE }, new List { 0xFE, 0xE0, 0x01, 0x1F, 0xFE, 0xF1, 0x01, 0x0E }, new List { 0xFE, 0xE0, 0x1F, 0x01, 0xFE, 0xF1, 0x0E, 0x01 }, new List { 0xFE, 0xE0, 0xE0, 0xFE, 0xFE, 0xF1, 0xF1, 0xFE }, new List { 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01 }, new List { 0xFE, 0xFE, 0x1F, 0x1F, 0xFE, 0xFE, 0x0E, 0x0E }, new List { 0xFE, 0xFE, 0xE0, 0xE0, 0xFE, 0xFE, 0xF1, 0xF1 } }; foreach (List weak in weakKeys) { if (weak.SequenceEqual(key)) { return Tuple.Create(ValidateResult.Warning, "This key is cryptographically weak"); } } List> guessableKeys = new List> { new List { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new List { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }, new List { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF } }; foreach (List weak in guessableKeys) { if (weak.SequenceEqual(key)) { return Tuple.Create(ValidateResult.Warning, "This key is easily guessable"); } } } else if (algId == (byte)AlgorithmId.AES256) { if (key.Count != 32) { return Tuple.Create(ValidateResult.Error, string.Format("Key length invalid - expected 32 bytes, got {0} bytes", key.Count)); } List> guessableKeys = new List> { new List { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new List { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }, new List { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF } }; foreach (List weak in guessableKeys) { if (weak.SequenceEqual(key)) { return Tuple.Create(ValidateResult.Warning, "This key is easily guessable"); } } } else if (algId == (byte)AlgorithmId.ADP) { if (key.Count != 5) { return Tuple.Create(ValidateResult.Error, string.Format("Key length invalid - expected 5 bytes, got {0} bytes", key.Count)); } List> guessableKeys = new List> { new List { 0x00, 0x00, 0x00, 0x00, 0x00 }, new List { 0x00, 0x01, 0x02, 0x03, 0x04 }, new List { 0x01, 0x23, 0x45, 0x67, 0x89 } }; foreach (List weak in guessableKeys) { if (weak.SequenceEqual(key)) { return Tuple.Create(ValidateResult.Warning, "This key is easily guessable"); } } } else // all other algorithm IDs { return Tuple.Create(ValidateResult.Warning, string.Format("Algorithm ID 0x{0:X2} is unassigned - no key validation has been performed", algId)); } // good practice validators if (sln == 0) { return Tuple.Create(ValidateResult.Warning, "While the SLN 0 is valid, some equipment may have issues using it"); // *cough* Motorola KVLs *cough* } if (sln >= 1 && sln <= 4095) { if (isKek) { return Tuple.Create(ValidateResult.Warning, "This SLN is in the range for TEKs, but the key type KEK is selected"); } } else if (sln >= 4096 && sln <= 61439) { return Tuple.Create(ValidateResult.Warning, "While this SLN is valid, it uses a crypto group other than 0 or 15, some equipment may have issues using it"); } else if (sln >= 61440 && sln <= 65535) { if (!isKek) { return Tuple.Create(ValidateResult.Warning, "This SLN is in the range for KEKs, but the key type TEK is selected"); } } return Tuple.Create(ValidateResult.Success, string.Empty); } } }