Exemplo n.º 1
0
        /// <summary>
        /// Loads a <see cref="ProtectedPrivateKey"/> from a JSON, according to Ethereum's
        /// <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">Web3
        /// Secret Storage Definition</a>.
        /// </summary>
        /// <param name="json">A JSON string that encodes a <see cref="ProtectedPrivateKey"/>.
        /// </param>
        /// <returns>A protected private key loaded from the given <paramref name="json"/>.
        /// </returns>
        /// <exception cref="JsonException">Thrown when the given <paramref name="json"/> is not
        /// a valid JSON.</exception>
        /// <exception cref="InvalidKeyJsonException">Thrown when the given key data lacks some
        /// required fields or consists of wrong types.</exception>
        /// <exception cref="UnsupportedKeyJsonException">Thrown when the given key data depends on
        /// an unsupported features (e.g., KDF).</exception>
        public static ProtectedPrivateKey FromJson(string json)
        {
            var options = new JsonDocumentOptions
            {
                AllowTrailingCommas = true,
                CommentHandling     = JsonCommentHandling.Skip,
            };

            using JsonDocument doc = JsonDocument.Parse(json, options);
            JsonElement rootElement = doc.RootElement;

            if (rootElement.ValueKind != JsonValueKind.Object)
            {
                throw new InvalidKeyJsonException(
                          "The root of the key JSON must be an object, but it is a/an " +
                          $"{rootElement.ValueKind}."
                          );
            }

            if (!rootElement.TryGetProperty("version", out JsonElement versionElement))
            {
                throw new InvalidKeyJsonException(
                          "The key JSON must contain \"version\" field, but it lacks."
                          );
            }

            if (versionElement.ValueKind != JsonValueKind.Number ||
                !versionElement.TryGetDecimal(out decimal versionNum))
            {
                throw new InvalidKeyJsonException("The \"version\" field must be a number.");
            }
            else if (versionNum != 3)
            {
                throw new UnsupportedKeyJsonException(
                          $"The key JSON format version {versionNum} is unsupported; " +
                          "Only version 3 is supported."
                          );
            }

            string GetStringProperty(JsonElement element, string fieldName)
            {
                if (!element.TryGetProperty(fieldName, out JsonElement fieldElement))
                {
                    throw new InvalidKeyJsonException(
                              $"The key JSON must contain \"{fieldName}\" field, but it lacks."
                              );
                }

                string str;

                try
                {
                    str = fieldElement.GetString();
                }
                catch (InvalidOperationException)
                {
                    throw new InvalidKeyJsonException(
                              $"The \"{fieldName}\" field must be a string."
                              );
                }

                if (str is null)
                {
                    throw new InvalidKeyJsonException(
                              $"The \"{fieldName}\" field must not be null, but a string."
                              );
                }

                return(str);
            }

            JsonElement GetObjectProperty(JsonElement element, string fieldName)
            {
                if (!element.TryGetProperty(fieldName, out var fieldElement))
                {
                    throw new InvalidKeyJsonException(
                              $"The key JSON must contain \"{fieldName}\" field, but it lacks."
                              );
                }
                else if (fieldElement.ValueKind != JsonValueKind.Object)
                {
                    throw new InvalidKeyJsonException(
                              $"The \"{fieldName}\" field must be an object, but it is a/an " +
                              $"{fieldElement.ValueKind}."
                              );
                }

                return(fieldElement);
            }

            byte[] GetHexProperty(JsonElement element, string fieldName)
            {
                string str = GetStringProperty(element, fieldName);

                byte[] bytes;
                try
                {
                    bytes = ByteUtil.ParseHex(str);
                }
                catch (Exception e)
                {
                    throw new InvalidKeyJsonException(
                              $"The \"{fieldName}\" field must be a hexadecimal string.\n{e}"
                              );
                }

                return(bytes);
            }

            JsonElement crypto              = GetObjectProperty(rootElement, "crypto");
            string      cipherType          = GetStringProperty(crypto, "cipher");
            JsonElement cipherParamsElement = GetObjectProperty(crypto, "cipherparams");

            byte[]      ciphertext       = GetHexProperty(crypto, "ciphertext");
            byte[]      mac              = GetHexProperty(crypto, "mac");
            string      kdfType          = GetStringProperty(crypto, "kdf");
            JsonElement kdfParamsElement = GetObjectProperty(crypto, "kdfparams");

            byte[]  addressBytes = GetHexProperty(rootElement, "address");
            Address address;

            try
            {
                address = new Address(addressBytes);
            }
            catch (ArgumentException e)
            {
                throw new InvalidKeyJsonException(
                          "The \"address\" field must contain an Ethereum-style address which " +
                          "consists of 40 hexadecimal letters: " + e
                          );
            }

            var cipher = cipherType switch
            {
                "aes-128-ctr" => Aes128Ctr.FromJson(cipherParamsElement),
                _ =>
                throw new UnsupportedKeyJsonException(
                          $"Unsupported cipher type: \"{cipherType}\".")
            };

            IKdf kdf;

            try
            {
                kdf = kdfType switch
                {
                    "pbkdf2" => Pbkdf2.FromJson(kdfParamsElement),
                    "scrypt" => Scrypt.FromJson(kdfParamsElement),
                    _ =>
                    throw new UnsupportedKeyJsonException(
                              $"Unsupported cipher type: \"{kdfType}\".")
                };
            }
            catch (ArgumentException e)
            {
                throw new InvalidKeyJsonException(e.Message);
            }

            return(new ProtectedPrivateKey(address, kdf, mac, cipher, ciphertext));
        }
Exemplo n.º 2
0
        public void FromJson()
        {
            var options = new JsonDocumentOptions
            {
                AllowTrailingCommas = true,
                CommentHandling     = JsonCommentHandling.Skip,
            };

            Pbkdf2 <Sha256Digest> Load(string json)
            {
                using (JsonDocument doc = JsonDocument.Parse(json, options))
                {
                    return((Pbkdf2 <Sha256Digest>)Pbkdf2.FromJson(doc.RootElement));
                }
            }

            var kdf = Load(@"
            {
                ""c"": 10240,
                ""dklen"": 32,
                ""prf"": ""hmac-sha256"",
                ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
            }
            ");

            Assert.Equal(10240, kdf.Iterations);
            Assert.Equal(32, kdf.KeyLength);
            TestUtils.AssertBytesEqual(
                new byte[]
            {
                0x3e, 0xea, 0xaf, 0x35, 0xda, 0x70, 0x92, 0x83, 0x87, 0xca, 0xe1,
                0xea, 0xd3, 0x1e, 0xd7, 0x82, 0xb1, 0x13, 0x5d, 0x75, 0x78, 0xa8,
                0x9d, 0x95, 0xe3, 0x0c, 0xc9, 0x14, 0x01, 0x0b, 0xa2, 0xed,
            }.ToImmutableArray(),
                kdf.Salt
                );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    // ""c"": 10240,  // lacks
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": true,  // not a number
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": null,  // not a number, but null
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    // ""dklen"": 32,  // lacks
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": false,  // not a number
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": null,  // not a number, but null
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    // ""prf"": ""hmac-sha256"",  // lacks
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    ""prf"": 123,  // not a string, but a number
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                    );

            Assert.Throws <UnsupportedKeyJsonException>(() =>
                                                        Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha512"",  // unsupported prf
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2ed"",
                }
                ")
                                                        );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    // ""salt"": ""..."",  // lacks
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    ""salt"": 1234,  // not a string, but a number
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""not a hexadecimal string"",
                }
                ")
                                                    );

            Assert.Throws <InvalidKeyJsonException>(() =>
                                                    Load(@"
                {
                    ""c"": 10240,
                    ""dklen"": 32,
                    ""prf"": ""hmac-sha256"",
                    ""salt"": ""3eeaaf35da70928387cae1ead31ed782b1135d7578a89d95e30cc914010ba2e"",
                    // salt: invalid length
                }
                ")
                                                    );
        }