/// <summary> /// Sign a file with a MinisignPrivateKey. /// </summary> /// <param name="fileToSign">The full path to the file.</param> /// <param name="minisignPrivateKey">A valid MinisignPrivateKey to sign.</param> /// <param name="untrustedComment">An optional untrusted comment.</param> /// <param name="trustedComment">An optional trusted comment.</param> /// <param name="outputFolder">The folder to write the signature (optional).</param> /// <returns>The full path to the signed file.</returns> /// <exception cref="FileNotFoundException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="IOException"></exception> /// <exception cref="UnauthorizedAccessException"></exception> /// <exception cref="SecurityException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="NotSupportedException"></exception> public static string Sign(string fileToSign, MinisignPrivateKey minisignPrivateKey, string untrustedComment = "", string trustedComment = "", string outputFolder = "") { if (fileToSign != null && !File.Exists(fileToSign)) { throw new FileNotFoundException("could not find fileToSign"); } if (minisignPrivateKey == null) { throw new ArgumentException("missing minisignPrivateKey input", nameof(minisignPrivateKey)); } if (string.IsNullOrEmpty(untrustedComment)) { untrustedComment = DefaultComment; } if (string.IsNullOrEmpty(trustedComment)) { var timestamp = GetTimestamp(); var filename = Path.GetFileName(fileToSign); trustedComment = "timestamp: " + timestamp + " file: " + filename; } if ((CommentPrefix + untrustedComment).Length > CommentMaxBytes) { throw new ArgumentOutOfRangeException(nameof(untrustedComment), "untrustedComment too long"); } if ((TrustedCommentPrefix + trustedComment).Length > TrustedCommentMaxBytes) { throw new ArgumentOutOfRangeException(nameof(trustedComment), "trustedComment too long"); } if (string.IsNullOrEmpty(outputFolder)) { outputFolder = Path.GetDirectoryName(fileToSign); } //validate the outputFolder if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder)) { throw new DirectoryNotFoundException("outputFolder must exist"); } if (outputFolder.IndexOfAny(Path.GetInvalidPathChars()) > -1) { throw new ArgumentException("The given path to the output folder contains invalid characters!"); } var file = LoadMessageFile(fileToSign); var minisignSignature = new MinisignSignature { KeyId = minisignPrivateKey.KeyId, SignatureAlgorithm = Encoding.UTF8.GetBytes(Sigalg) }; var signature = PublicKeyAuth.SignDetached(file, minisignPrivateKey.SecretKey); minisignSignature.Signature = signature; var binarySignature = ArrayHelpers.ConcatArrays( minisignSignature.SignatureAlgorithm, minisignSignature.KeyId, minisignSignature.Signature ); // sign the signature and the trusted comment with a global signature var globalSignature = PublicKeyAuth.SignDetached( ArrayHelpers.ConcatArrays(minisignSignature.Signature, Encoding.UTF8.GetBytes(trustedComment)), minisignPrivateKey.SecretKey); // prepare the file lines var signatureFileContent = new[] { CommentPrefix + untrustedComment, Convert.ToBase64String(binarySignature), TrustedCommentPrefix + trustedComment, Convert.ToBase64String(globalSignature) }; var outputFile = fileToSign + SigSuffix; File.WriteAllLines(outputFile, signatureFileContent); return(outputFile); }
/// <summary> /// Load a public key into a MinisignPublicKey object. /// </summary> /// <param name="privateKey">A valid private key.</param> /// <param name="password">The password to decrypt the private key.</param> /// <returns>A MinisignPrivateKey object.</returns> /// <exception cref="OverflowException"></exception> /// <exception cref="CorruptPrivateKeyException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentNullException"></exception> public static MinisignPrivateKey LoadPrivateKey(byte[] privateKey, byte[] password) { if (privateKey == null) { throw new ArgumentException("missing privateKey input", nameof(privateKey)); } if (password == null) { throw new ArgumentException("missing password input", nameof(password)); } var minisignPrivateKey = new MinisignPrivateKey { SignatureAlgorithm = ArrayHelpers.SubArray(privateKey, 0, 2), KdfAlgorithm = ArrayHelpers.SubArray(privateKey, 2, 2), ChecksumAlgorithm = ArrayHelpers.SubArray(privateKey, 4, 2), KdfSalt = ArrayHelpers.SubArray(privateKey, 6, 32), KdfOpsLimit = BitConverter.ToInt64(ArrayHelpers.SubArray(privateKey, 38, 8), 0), //currently unused KdfMemLimit = BitConverter.ToInt64(ArrayHelpers.SubArray(privateKey, 46, 8), 0) //currently unused }; if (!minisignPrivateKey.SignatureAlgorithm.SequenceEqual(Encoding.UTF8.GetBytes(Sigalg))) { throw new CorruptPrivateKeyException("bad SignatureAlgorithm"); } if (!minisignPrivateKey.ChecksumAlgorithm.SequenceEqual(Encoding.UTF8.GetBytes(Chkalg))) { throw new CorruptPrivateKeyException("bad ChecksumAlgorithm"); } if (!minisignPrivateKey.KdfAlgorithm.SequenceEqual(Encoding.UTF8.GetBytes(Kdfalg))) { throw new CorruptPrivateKeyException("bad KdfAlgorithm"); } if (minisignPrivateKey.KdfSalt.Length != KeySaltBytes) { throw new CorruptPrivateKeyException("bad KdfSalt length"); } var encryptedKeyData = ArrayHelpers.SubArray(privateKey, 54, 104); var decryptionKey = PasswordHash.ScryptHashBinary(password, minisignPrivateKey.KdfSalt, PasswordHash.Strength.Sensitive, 104); var decryptedKeyData = EncryptionHelpers.Xor(encryptedKeyData, decryptionKey); minisignPrivateKey.KeyId = ArrayHelpers.SubArray(decryptedKeyData, 0, 8); minisignPrivateKey.SecretKey = ArrayHelpers.SubArray(decryptedKeyData, 8, 64); minisignPrivateKey.Checksum = ArrayHelpers.SubArray(decryptedKeyData, 72, 32); if (minisignPrivateKey.KeyId.Length != KeyNumBytes) { throw new CorruptPrivateKeyException("bad KeyId length"); } var calculatedChecksum = GenericHash.Hash( ArrayHelpers.ConcatArrays(minisignPrivateKey.SignatureAlgorithm, minisignPrivateKey.KeyId, minisignPrivateKey.SecretKey), null, 32); if (!ArrayHelpers.ConstantTimeEquals(minisignPrivateKey.Checksum, calculatedChecksum)) { throw new CorruptPrivateKeyException("bad private key checksum"); } // extract the public key from the private key minisignPrivateKey.PublicKey = PublicKeyAuth.ExtractEd25519PublicKeyFromEd25519SecretKey(minisignPrivateKey.SecretKey); return(minisignPrivateKey); }
/// <summary> /// Generate a new Minisign key pair. /// </summary> /// <param name="password">The password to protect the secret key.</param> /// <param name="writeOutputFiles">If false, no files will be written.</param> /// <param name="outputFolder">The folder to write the files (optional).</param> /// <param name="keyPairFileName">The name of the files to write (optional).</param> /// <returns>A MinisignKeyPair object.</returns> /// <exception cref="ArgumentException"></exception> /// <exception cref="OverflowException"></exception> /// <exception cref="DirectoryNotFoundException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="IOException"></exception> /// <exception cref="UnauthorizedAccessException"></exception> /// <exception cref="PathTooLongException"></exception> /// <exception cref="SecurityException"></exception> /// <exception cref="NotSupportedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public static MinisignKeyPair GenerateKeyPair(string password, bool writeOutputFiles = false, string outputFolder = "", string keyPairFileName = "minisign") { if (string.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password), "password can not be null"); } if (writeOutputFiles) { //validate the outputFolder if (string.IsNullOrEmpty(outputFolder) || !Directory.Exists(outputFolder)) { throw new DirectoryNotFoundException("outputFolder must exist"); } if (outputFolder.IndexOfAny(Path.GetInvalidPathChars()) > -1) { throw new ArgumentException("The given path to the output folder contains invalid characters!"); } //validate the keyPairFileName if (string.IsNullOrEmpty(keyPairFileName)) { throw new ArgumentNullException(nameof(keyPairFileName), "keyPairFileName can not be empty"); } } var minisignKeyPair = new MinisignKeyPair(); var minisignPrivateKey = new MinisignPrivateKey(); var keyPair = PublicKeyAuth.GenerateKeyPair(); var keyId = SodiumCore.GetRandomBytes(KeyNumBytes); var kdfSalt = SodiumCore.GetRandomBytes(32); minisignPrivateKey.PublicKey = keyPair.PublicKey; minisignPrivateKey.KdfSalt = kdfSalt; minisignPrivateKey.SignatureAlgorithm = Encoding.UTF8.GetBytes(Sigalg); minisignPrivateKey.ChecksumAlgorithm = Encoding.UTF8.GetBytes(Chkalg); minisignPrivateKey.KdfAlgorithm = Encoding.UTF8.GetBytes(Kdfalg); minisignPrivateKey.KdfMemLimit = 1073741824; //currently unused minisignPrivateKey.KdfOpsLimit = 33554432; //currently unused var checksum = GenericHash.Hash( ArrayHelpers.ConcatArrays(minisignPrivateKey.SignatureAlgorithm, keyId, keyPair.PrivateKey), null, 32); minisignPrivateKey.KeyId = keyId; minisignPrivateKey.SecretKey = keyPair.PrivateKey; minisignPrivateKey.Checksum = checksum; var dataToProtect = ArrayHelpers.ConcatArrays(keyId, keyPair.PrivateKey, checksum); var encryptionKey = PasswordHash.ScryptHashBinary(Encoding.UTF8.GetBytes(password), minisignPrivateKey.KdfSalt, PasswordHash.Strength.Sensitive, 104); var encryptedKeyData = EncryptionHelpers.Xor(dataToProtect, encryptionKey); // set up the public key var minisignPublicKey = new MinisignPublicKey { KeyId = keyId, PublicKey = keyPair.PublicKey, SignatureAlgorithm = Encoding.UTF8.GetBytes(Sigalg) }; keyPair.Dispose(); if (writeOutputFiles) { var privateKeyOutputFileName = Path.Combine(outputFolder, keyPairFileName + PrivateKeyFileSuffix); var publicKeyOutputFileName = Path.Combine(outputFolder, keyPairFileName + PublicKeyFileSuffix); var binaryPublicKey = ArrayHelpers.ConcatArrays( minisignPublicKey.SignatureAlgorithm, minisignPublicKey.KeyId, minisignPublicKey.PublicKey ); var publicFileContent = new[] { CommentPrefix + "minisign public key " + Utilities.BinaryToHex(minisignPublicKey.KeyId, Utilities.HexFormat.None, Utilities.HexCase.Upper), Convert.ToBase64String(binaryPublicKey) }; var binaryPrivateKey = ArrayHelpers.ConcatArrays( minisignPrivateKey.SignatureAlgorithm, minisignPrivateKey.KdfAlgorithm, minisignPrivateKey.ChecksumAlgorithm, minisignPrivateKey.KdfSalt, BitConverter.GetBytes(minisignPrivateKey.KdfOpsLimit), BitConverter.GetBytes(minisignPrivateKey.KdfMemLimit), encryptedKeyData ); var privateFileContent = new[] { CommentPrefix + PrivateKeyDefaultComment, Convert.ToBase64String(binaryPrivateKey) }; // files will be overwritten! File.WriteAllLines(publicKeyOutputFileName, publicFileContent); File.WriteAllLines(privateKeyOutputFileName, privateFileContent); minisignKeyPair.MinisignPublicKeyFilePath = publicKeyOutputFileName; minisignKeyPair.MinisignPrivateKeyFilePath = privateKeyOutputFileName; } minisignKeyPair.MinisignPublicKey = minisignPublicKey; minisignKeyPair.MinisignPrivateKey = minisignPrivateKey; return(minisignKeyPair); }