PageRenderTime 58ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 2ms

/Security/GPG.cs

https://bitbucket.org/AdamMil/adammil.net
C# | 5775 lines | 4756 code | 581 blank | 438 comment | 1152 complexity | b51005ae4555811860c2929a6b59822a MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. AdamMil.Security is a .NET library providing OpenPGP-based security.
  3. http://www.adammil.net/
  4. Copyright (C) 2008-2013 Adam Milazzo
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  16. */
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Diagnostics;
  20. using System.Globalization;
  21. using System.IO;
  22. using System.Text;
  23. using System.Text.RegularExpressions;
  24. using System.Threading;
  25. using AdamMil.Collections;
  26. using AdamMil.IO;
  27. using AdamMil.Security.PGP.GPG.StatusMessages;
  28. using AdamMil.Utilities;
  29. using Microsoft.Win32.SafeHandles;
  30. using SecureString=System.Security.SecureString;
  31. namespace AdamMil.Security.PGP.GPG
  32. {
  33. /// <summary>Processes text output from GPG.</summary>
  34. public delegate void TextLineHandler(string line);
  35. #region GPG
  36. /// <summary>A base class to aid in the implementation of interfaces to the GNU Privacy Guard (GPG).</summary>
  37. public abstract class GPG : PGPSystem
  38. {
  39. /// <summary>Parses an argument from a GPG status message into a cipher name, or null if the cipher type cannot be
  40. /// determined.
  41. /// </summary>
  42. public static string ParseCipher(string str)
  43. {
  44. switch((OpenPGPCipher)int.Parse(str, CultureInfo.InvariantCulture))
  45. {
  46. case OpenPGPCipher.AES: return SymmetricCipher.AES;
  47. case OpenPGPCipher.AES192: return SymmetricCipher.AES192;
  48. case OpenPGPCipher.AES256: return SymmetricCipher.AES256;
  49. case OpenPGPCipher.Blowfish: return SymmetricCipher.Blowfish;
  50. case OpenPGPCipher.CAST5: return SymmetricCipher.CAST5;
  51. case OpenPGPCipher.IDEA: return SymmetricCipher.IDEA;
  52. case OpenPGPCipher.TripleDES: return SymmetricCipher.TripleDES;
  53. case OpenPGPCipher.Twofish: return SymmetricCipher.Twofish;
  54. case OpenPGPCipher.DESSK: return "DESSK";
  55. case OpenPGPCipher.SAFER: return "SAFER";
  56. case OpenPGPCipher.Unencrypted: return "Unencrypted";
  57. default: return string.IsNullOrEmpty(str) ? null : str;
  58. }
  59. }
  60. /// <summary>Parses an argument from a GPG status message into a hash algorithm name, or null if the algorithm cannot
  61. /// be determined.
  62. /// </summary>
  63. public static string ParseHashAlgorithm(string str)
  64. {
  65. switch((OpenPGPHashAlgorithm)int.Parse(str, CultureInfo.InvariantCulture))
  66. {
  67. case OpenPGPHashAlgorithm.MD5: return HashAlgorithm.MD5;
  68. case OpenPGPHashAlgorithm.RIPEMD160: return HashAlgorithm.RIPEMD160;
  69. case OpenPGPHashAlgorithm.SHA1: return HashAlgorithm.SHA1;
  70. case OpenPGPHashAlgorithm.SHA224: return HashAlgorithm.SHA224;
  71. case OpenPGPHashAlgorithm.SHA256: return HashAlgorithm.SHA256;
  72. case OpenPGPHashAlgorithm.SHA384: return HashAlgorithm.SHA384;
  73. case OpenPGPHashAlgorithm.SHA512: return HashAlgorithm.SHA512;
  74. case OpenPGPHashAlgorithm.HAVAL: return "HAVAL-5-160";
  75. case OpenPGPHashAlgorithm.MD2: return "MD2";
  76. case OpenPGPHashAlgorithm.TIGER192: return "TIGER192";
  77. default: return string.IsNullOrEmpty(str) ? null : str;
  78. }
  79. }
  80. /// <summary>Parses an argument from a GPG status message into a key type name, or null if the key type cannot
  81. /// be determined.
  82. /// </summary>
  83. public static string ParseKeyType(string str)
  84. {
  85. switch((OpenPGPKeyType)int.Parse(str, CultureInfo.InvariantCulture))
  86. {
  87. case OpenPGPKeyType.DSA:
  88. return KeyType.DSA;
  89. case OpenPGPKeyType.ElGamal: case OpenPGPKeyType.ElGamalEncryptOnly:
  90. return KeyType.ElGamal;
  91. case OpenPGPKeyType.RSA: case OpenPGPKeyType.RSAEncryptOnly: case OpenPGPKeyType.RSASignOnly:
  92. return KeyType.RSA;
  93. default: return string.IsNullOrEmpty(str) ? null : str;
  94. }
  95. }
  96. /// <summary>Parses an argument from a GPG status message into a timestamp.</summary>
  97. public static DateTime ParseTimestamp(string str)
  98. {
  99. if(str.IndexOf('T') == -1) // the time is specified in seconds since Midnight, January 1, 1970
  100. {
  101. long seconds = long.Parse(str, CultureInfo.InvariantCulture);
  102. return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(seconds);
  103. }
  104. else // the date is in ISO8601 format. DateTime.Parse() can handle it.
  105. {
  106. return DateTime.Parse(str, CultureInfo.InvariantCulture);
  107. }
  108. }
  109. /// <summary>Parses an argument from a GPG status message into a timestamp, or null if there is no timestamp.</summary>
  110. public static DateTime? ParseNullableTimestamp(string str)
  111. {
  112. return string.IsNullOrEmpty(str) || str.Equals("0", StringComparison.Ordinal) ?
  113. (DateTime?)null : ParseTimestamp(str);
  114. }
  115. }
  116. #endregion
  117. #region ExeGPG
  118. /// <summary>This class implements a connection to the GNU Privacy Guard via piping input to and from its command-line
  119. /// executable.
  120. /// </summary>
  121. public class ExeGPG : GPG
  122. {
  123. /// <summary>Initializes a new <see cref="ExeGPG"/> with no reference to the GPG executable.</summary>
  124. public ExeGPG() { }
  125. /// <summary>Initializes a new <see cref="ExeGPG"/> with a full path to the GPG executable.</summary>
  126. public ExeGPG(string exePath)
  127. {
  128. Initialize(exePath);
  129. }
  130. /// <summary>Raised when a line of text is to be logged.</summary>
  131. public event TextLineHandler LineLogged;
  132. /// <summary>Gets or sets whether the GPG agent will be used. If enabled, GPG may use its own user interface to query
  133. /// for passwords, bypassing the support provided by this library. The default is false. However, this property has
  134. /// no effect when using GPG2, because GPG2 doesn't allow the agent to be disabled.
  135. /// </summary>
  136. public bool EnableGPGAgent
  137. {
  138. get { return enableAgent; }
  139. set { enableAgent = value; }
  140. }
  141. /// <summary>Gets the path to the GPG executable, or null if <see cref="Initialize"/> has not been called.</summary>
  142. public string ExecutablePath
  143. {
  144. get { return exePath; }
  145. }
  146. /// <summary>Gets or sets whether the <see cref="SignatureBase.KeyFingerprint">KeySignature.KeyFingerprint</see>
  147. /// field will be retrieved. According to the GPG documentation, GPG won't return fingerprints on key signatures
  148. /// unless signature verification is enabled and signature caching is disabled, due to "various technical reasons".
  149. /// Checking the signatures and disabling the cache causes a significant performance hit, however, so by default it
  150. /// is not done. If this property is set to true, the cache will be disabled and signature verification will be
  151. /// enabled on all signature retrievals, allowing GPG to return the key signature fingerprint. Note that even with
  152. /// this property set to true, the fingerprint still won't be set if the key signature failed verification.
  153. /// </summary>
  154. public bool RetrieveKeySignatureFingerprints
  155. {
  156. get { return retrieveKeySignatureFingerprints; }
  157. set { retrieveKeySignatureFingerprints = value; }
  158. }
  159. /// <summary>Gets the version of the GPG executable, encoded as an integer so that 1.4.9 becomes 10409 and 2.0.21 becomes
  160. /// 20021. Note that the version number is retrieved when this class is instantiated with an executable and whenever
  161. /// <see cref="Initialize"/> is called. If a newer version of GPG is installed in the mean time, the version reported by this
  162. /// property will not be updated until <see cref="Initialize"/> is called again.
  163. /// </summary>
  164. public int Version
  165. {
  166. get { return gpgVersion; }
  167. }
  168. #region Configuration
  169. /// <include file="documentation.xml" path="/Security/PGPSystem/GetDefaultPrimaryKeyType/node()"/>
  170. public override string GetDefaultPrimaryKeyType()
  171. {
  172. return KeyType.DSA;
  173. }
  174. /// <include file="documentation.xml" path="/Security/PGPSystem/GetDefaultSubkeyType/node()"/>
  175. public override string GetDefaultSubkeyType()
  176. {
  177. return KeyType.ElGamal;
  178. }
  179. /// <include file="documentation.xml" path="/Security/PGPSystem/GetMaximumKeyLength/node()"/>
  180. public override int GetMaximumKeyLength(string keyType)
  181. {
  182. if(!string.Equals(keyType, "RSA-E", StringComparison.OrdinalIgnoreCase) &&
  183. !string.Equals(keyType, "RSA-S", StringComparison.OrdinalIgnoreCase) &&
  184. !string.Equals(keyType, "ELG-E", StringComparison.OrdinalIgnoreCase) &&
  185. !string.Equals(keyType, "ELG", StringComparison.OrdinalIgnoreCase))
  186. {
  187. AssertSupported(keyType, keyTypes, "key type");
  188. }
  189. return string.Equals(keyType, "DSA", StringComparison.OrdinalIgnoreCase) ? 3072 : 4096;
  190. }
  191. /// <include file="documentation.xml" path="/Security/PGPSystem/GetSupportedCiphers/node()"/>
  192. public override string[] GetSupportedCiphers()
  193. {
  194. AssertInitialized();
  195. return ciphers == null ? new string[0] : (string[])ciphers.Clone();
  196. }
  197. /// <include file="documentation.xml" path="/Security/PGPSystem/GetSupportedCompressions/node()"/>
  198. public override string[] GetSupportedCompressions()
  199. {
  200. AssertInitialized();
  201. return compressions == null ? new string[0] : (string[])compressions.Clone();
  202. }
  203. /// <include file="documentation.xml" path="/Security/PGPSystem/GetSupportedHashes/node()"/>
  204. public override string[] GetSupportedHashes()
  205. {
  206. AssertInitialized();
  207. return hashes == null ? new string[0] : (string[])hashes.Clone();
  208. }
  209. /// <include file="documentation.xml" path="/Security/PGPSystem/GetSupportedKeyTypes/node()"/>
  210. public override string[] GetSupportedKeyTypes()
  211. {
  212. AssertInitialized();
  213. return keyTypes == null ? new string[0] : (string[])keyTypes.Clone();
  214. }
  215. #endregion
  216. #region Encryption and signing
  217. /// <include file="documentation.xml" path="/Security/PGPSystem/SignAndEncrypt/node()"/>
  218. public override void SignAndEncrypt(Stream sourceData, Stream destination, SigningOptions signingOptions,
  219. EncryptionOptions encryptionOptions, OutputOptions outputOptions)
  220. {
  221. if(sourceData == null || destination == null || encryptionOptions == null && signingOptions == null)
  222. {
  223. throw new ArgumentNullException();
  224. }
  225. string args = GetOutputArgs(outputOptions);
  226. bool symmetric = false; // whether we're doing password-based encryption (possibly in addition to key-based)
  227. bool customAlgo = false; // whether a custom algorithm was specified
  228. // add the keyrings of all the recipient and signer keys to the command line
  229. List<PrimaryKey> keyringKeys = new List<PrimaryKey>();
  230. if(encryptionOptions != null) // if we'll be doing any encryption
  231. {
  232. // we can't do signing with detached signatures because GPG doesn't have a way to specify the two output files.
  233. // and encryption with
  234. if(signingOptions != null && signingOptions.Type != SignatureType.Embedded)
  235. {
  236. if(signingOptions.Type == SignatureType.ClearSignedText)
  237. {
  238. throw new ArgumentException("Combining encryption with clear-signing does not make sense, because the data "+
  239. "cannot be both encrypted and in the clear.");
  240. }
  241. else
  242. {
  243. throw new NotSupportedException("Simultaneous encryption and detached signing is not supported by GPG. "+
  244. "Perform the encryption and detached signing as two separate steps.");
  245. }
  246. }
  247. symmetric = encryptionOptions.Password != null && encryptionOptions.Password.Length != 0;
  248. // we need recipients if we're not doing password-based encryption
  249. if(!symmetric && encryptionOptions.Recipients.Count == 0 && encryptionOptions.HiddenRecipients.Count == 0)
  250. {
  251. throw new ArgumentException("No recipients were specified.");
  252. }
  253. keyringKeys.AddRange(encryptionOptions.Recipients);
  254. keyringKeys.AddRange(encryptionOptions.HiddenRecipients);
  255. // if there are recipients for key-based encryption, add them to the command line
  256. if(encryptionOptions.Recipients.Count != 0 || encryptionOptions.HiddenRecipients.Count != 0)
  257. {
  258. args += GetFingerprintArgs(encryptionOptions.Recipients, "-r") +
  259. GetFingerprintArgs(encryptionOptions.HiddenRecipients, "-R") + "-e "; // plus the encrypt command
  260. }
  261. if(!string.IsNullOrEmpty(encryptionOptions.Cipher))
  262. {
  263. AssertSupported(encryptionOptions.Cipher, ciphers, "cipher");
  264. args += "--cipher-algo " + EscapeArg(encryptionOptions.Cipher) + " ";
  265. customAlgo = true;
  266. }
  267. if(symmetric) args += "-c "; // add the password-based encryption command if necessary
  268. if(encryptionOptions.AlwaysTrustRecipients) args += "--trust-model always ";
  269. }
  270. if(signingOptions != null) // if we'll be doing any signing
  271. {
  272. if(signingOptions.Signers.Count == 0) throw new ArgumentException("No signers were specified.");
  273. // add the keyrings of the signers to the command prompt
  274. keyringKeys.AddRange(signingOptions.Signers);
  275. if(!string.IsNullOrEmpty(signingOptions.Hash))
  276. {
  277. AssertSupported(encryptionOptions.Cipher, hashes, "hash");
  278. args += "--digest-algo "+EscapeArg(signingOptions.Hash)+" ";
  279. customAlgo = true;
  280. }
  281. // add all of the signers to the command line, and the signing command
  282. args += GetFingerprintArgs(signingOptions.Signers, "-u") +
  283. (signingOptions.Type == SignatureType.Detached ? "-b " :
  284. signingOptions.Type == SignatureType.ClearSignedText ? "--clearsign " : "-s ");
  285. }
  286. args += GetKeyringArgs(keyringKeys, true); // add all the keyrings to the command line
  287. Command command = Execute(args, StatusMessages.ReadInBackground, false);
  288. CommandState commandState = new CommandState(command);
  289. if(customAlgo) commandState.FailureReasons |= FailureReason.UnsupportedAlgorithm; // using a custom algo can cause failure
  290. using(ManualResetEvent ready = new ManualResetEvent(false)) // create an event to signal when the data should be sent
  291. {
  292. ProcessCommand(command, commandState,
  293. delegate(Command cmd, CommandState state)
  294. {
  295. cmd.InputNeeded += delegate(string promptId)
  296. {
  297. if(string.Equals(promptId, "untrusted_key.override", StringComparison.Ordinal))
  298. { // this question indicates that a recipient key is not trusted
  299. bool alwaysTrust = encryptionOptions != null && encryptionOptions.AlwaysTrustRecipients;
  300. if(!alwaysTrust) state.FailureReasons |= FailureReason.UntrustedRecipient;
  301. cmd.SendLine(alwaysTrust ? "Y" : "N");
  302. }
  303. else if(string.Equals(promptId, "passphrase.enter", StringComparison.Ordinal) &&
  304. state.PasswordMessage != null && state.PasswordMessage.Type == StatusMessageType.NeedCipherPassphrase)
  305. {
  306. cmd.SendPassword(encryptionOptions.Password, false);
  307. }
  308. else if(!state.Canceled)
  309. {
  310. DefaultPromptHandler(promptId, state);
  311. if(state.Canceled) cmd.Kill(); // kill GPG if the user doesn't give the password, so it doesn't keep asking
  312. }
  313. };
  314. cmd.StatusMessageReceived += delegate(StatusMessage msg)
  315. {
  316. switch(msg.Type)
  317. {
  318. case StatusMessageType.BeginEncryption: case StatusMessageType.BeginSigning:
  319. ready.Set(); // all set. send the data!
  320. break;
  321. default: DefaultStatusMessageHandler(msg, state); break;
  322. }
  323. };
  324. },
  325. delegate(Command cmd, CommandState state)
  326. {
  327. // wait until it's time to write the data or the process aborted
  328. while(!ready.WaitOne(50, false) && !cmd.Process.HasExited) { }
  329. // if the process is still running and it didn't exit before we could copy the input data...
  330. if(!cmd.Process.HasExited) ReadAndWriteStreams(destination, sourceData, cmd.Process);
  331. });
  332. }
  333. if(!command.SuccessfulExit) // if the process wasn't successful, throw an exception
  334. {
  335. if(commandState.Canceled) throw new OperationCanceledException();
  336. else if(encryptionOptions != null) throw new EncryptionFailedException(commandState.FailureReasons);
  337. else throw new SigningFailedException(commandState.FailureReasons);
  338. }
  339. }
  340. /// <include file="documentation.xml" path="/Security/PGPSystem/Decrypt/node()"/>
  341. public override Signature[] Decrypt(Stream ciphertext, Stream destination, DecryptionOptions options)
  342. {
  343. if(ciphertext == null || destination == null) throw new ArgumentNullException();
  344. Command cmd = Execute(GetVerificationArgs(options, true) + "-d", StatusMessages.ReadInBackground, false);
  345. return DecryptVerifyCore(cmd, ciphertext, destination, options);
  346. }
  347. /// <include file="documentation.xml" path="/Security/PGPSystem/Verify2/node()"/>
  348. public override Signature[] Verify(Stream signedData, VerificationOptions options)
  349. {
  350. if(signedData == null) throw new ArgumentNullException();
  351. return VerifyCore(null, signedData, options);
  352. }
  353. /// <include file="documentation.xml" path="/Security/PGPSystem/Verify3/node()"/>
  354. /// <remarks>The signature data (from <paramref name="signature"/>) will be written into a temporary file for the
  355. /// duration of this method call.
  356. /// </remarks>
  357. public override Signature[] Verify(Stream signedData, Stream signature, VerificationOptions options)
  358. {
  359. if(signedData == null || signature == null) throw new ArgumentNullException();
  360. // copy the signature into a temporary file, because we can't pass both streams on standard input
  361. string sigFileName = Path.GetTempFileName();
  362. try
  363. {
  364. using(FileStream file = new FileStream(sigFileName, FileMode.Truncate, FileAccess.Write))
  365. {
  366. signature.CopyTo(file);
  367. }
  368. return VerifyCore(sigFileName, signedData, options);
  369. }
  370. finally { File.Delete(sigFileName); }
  371. }
  372. #endregion
  373. #region Key import and export
  374. /// <include file="documentation.xml" path="/Security/PGPSystem/ExportKeys/node()"/>
  375. public override void ExportKeys(PrimaryKey[] keys, Stream destination, ExportOptions exportOptions,
  376. OutputOptions outputOptions)
  377. {
  378. if(keys == null || destination == null) throw new ArgumentNullException();
  379. if((exportOptions & (ExportOptions.ExportPublicKeys|ExportOptions.ExportSecretKeys)) == 0)
  380. {
  381. throw new ArgumentException("At least one of ExportOptions.ExportPublicKeys or ExportOptions.ExportSecretKeys "+
  382. "must be specified.");
  383. }
  384. if(keys.Length == 0) return;
  385. if((exportOptions & ExportOptions.ExportPublicKeys) != 0)
  386. {
  387. string args = GetKeyringArgs(keys, false) + GetExportArgs(exportOptions, false, true) +
  388. GetOutputArgs(outputOptions) + GetFingerprintArgs(keys);
  389. ExportCore(args, destination);
  390. }
  391. if((exportOptions & ExportOptions.ExportSecretKeys) != 0)
  392. {
  393. string args = GetKeyringArgs(keys, true) + GetExportArgs(exportOptions, true, true) +
  394. GetOutputArgs(outputOptions) + GetFingerprintArgs(keys);
  395. ExportCore(args, destination);
  396. }
  397. }
  398. /// <include file="documentation.xml" path="/Security/PGPSystem/ExportKeys2/node()"/>
  399. public override void ExportKeys(Keyring[] keyrings, bool includeDefaultKeyring, Stream destination,
  400. ExportOptions exportOptions, OutputOptions outputOptions)
  401. {
  402. if(destination == null) throw new ArgumentNullException();
  403. if((exportOptions & (ExportOptions.ExportPublicKeys|ExportOptions.ExportSecretKeys)) == 0)
  404. {
  405. throw new ArgumentException("At least one of ExportOptions.ExportPublicKeys or ExportOptions.ExportSecretKeys "+
  406. "must be specified.");
  407. }
  408. if((exportOptions & ExportOptions.ExportPublicKeys) != 0)
  409. {
  410. string args = GetKeyringArgs(keyrings, !includeDefaultKeyring, false) +
  411. GetExportArgs(exportOptions, false, true) + GetOutputArgs(outputOptions);
  412. ExportCore(args, destination);
  413. }
  414. if((exportOptions & ExportOptions.ExportSecretKeys) != 0)
  415. {
  416. string args = GetKeyringArgs(keyrings, !includeDefaultKeyring, true) +
  417. GetExportArgs(exportOptions, true, true) + GetOutputArgs(outputOptions);
  418. ExportCore(args, destination);
  419. }
  420. }
  421. /// <include file="documentation.xml" path="/Security/PGPSystem/ImportKeys3/node()"/>
  422. public override ImportedKey[] ImportKeys(Stream source, Keyring keyring, ImportOptions options)
  423. {
  424. if(source == null) throw new ArgumentNullException();
  425. CommandState state;
  426. Command cmd = Execute(GetImportArgs(keyring, options) + "--import", StatusMessages.ReadInBackground, false);
  427. ImportedKey[] keys = ImportCore(cmd, source, out state);
  428. if(!cmd.SuccessfulExit) throw new ImportFailedException(state.FailureReasons);
  429. return keys;
  430. }
  431. #endregion
  432. #region Key revocation
  433. /// <include file="documentation.xml" path="/Security/PGPSystem/AddDesignatedRevoker/node()"/>
  434. public override void AddDesignatedRevoker(PrimaryKey key, PrimaryKey revokerKey)
  435. {
  436. if(key == null || revokerKey == null) throw new ArgumentNullException();
  437. if(string.IsNullOrEmpty(revokerKey.Fingerprint))
  438. {
  439. throw new ArgumentException("The revoker key has no fingerprint.");
  440. }
  441. if(string.Equals(key.Fingerprint, revokerKey.Fingerprint, StringComparison.Ordinal))
  442. {
  443. throw new ArgumentException("You can't add a key as its own designated revoker.");
  444. }
  445. DoEdit(key, GetKeyringArgs(new PrimaryKey[] { key, revokerKey }, true), false,
  446. new AddRevokerCommand(revokerKey.Fingerprint));
  447. }
  448. /// <include file="documentation.xml" path="/Security/PGPSystem/GenerateRevocationCertificate/node()"/>
  449. public override void GenerateRevocationCertificate(PrimaryKey key, Stream destination, KeyRevocationReason reason,
  450. OutputOptions outputOptions)
  451. {
  452. GenerateRevocationCertificateCore(key, null, destination, reason, outputOptions);
  453. }
  454. /// <include file="documentation.xml" path="/Security/PGPSystem/GenerateRevocationCertificateD/node()"/>
  455. public override void GenerateRevocationCertificate(PrimaryKey keyToRevoke, PrimaryKey designatedRevoker,
  456. Stream destination, KeyRevocationReason reason,
  457. OutputOptions outputOptions)
  458. {
  459. if(designatedRevoker == null) throw new ArgumentNullException();
  460. GenerateRevocationCertificateCore(keyToRevoke, designatedRevoker, destination, reason, outputOptions);
  461. }
  462. /// <include file="documentation.xml" path="/Security/PGPSystem/RevokeKeys/node()"/>
  463. public override void RevokeKeys(KeyRevocationReason reason, params PrimaryKey[] keys)
  464. {
  465. RevokeKeysCore(null, reason, keys);
  466. }
  467. /// <include file="documentation.xml" path="/Security/PGPSystem/RevokeKeysD/node()"/>
  468. public override void RevokeKeys(PrimaryKey designatedRevoker, KeyRevocationReason reason, params PrimaryKey[] keys)
  469. {
  470. if(designatedRevoker == null) throw new ArgumentNullException();
  471. RevokeKeysCore(designatedRevoker, reason, keys);
  472. }
  473. /// <include file="documentation.xml" path="/Security/PGPSystem/RevokeSubkeys/node()"/>
  474. public override void RevokeSubkeys(KeyRevocationReason reason, params Subkey[] subkeys)
  475. {
  476. EditSubkeys(subkeys, delegate { return new RevokeSubkeysCommand(reason); });
  477. }
  478. #endregion
  479. #region Key server operations
  480. /// <include file="documentation.xml" path="/Security/PGPSystem/FindKeysOnServer/node()"/>
  481. public override void FindKeysOnServer(Uri keyServer, KeySearchHandler handler, params string[] searchKeywords)
  482. {
  483. if(keyServer == null || handler == null || searchKeywords == null) throw new ArgumentNullException();
  484. if(searchKeywords.Length == 0) throw new ArgumentException("No keywords were given.");
  485. string args = "--keyserver " + EscapeArg(keyServer.AbsoluteUri) + " --with-colons --fixed-list-mode --search-keys";
  486. foreach(string keyword in searchKeywords) args += " " + EscapeArg(keyword);
  487. Command command = Execute(args, StatusMessages.MixIntoStdout, true, true);
  488. CommandState commandState = ProcessCommand(command,
  489. delegate(Command cmd, CommandState state)
  490. {
  491. cmd.StandardErrorLine += delegate(string line) { DefaultStandardErrorHandler(line, state); };
  492. },
  493. delegate(Command cmd, CommandState state)
  494. {
  495. List<PrimaryKey> keysFound = new List<PrimaryKey>();
  496. List<UserId> userIds = new List<UserId>();
  497. while(true)
  498. {
  499. string line;
  500. cmd.ReadLine(out line);
  501. if(line != null) LogLine(line);
  502. gotLine:
  503. if(line == null && cmd.StatusMessage == null) break;
  504. if(line == null)
  505. {
  506. switch(cmd.StatusMessage.Type)
  507. {
  508. case StatusMessageType.GetLine:
  509. GetInputMessage m = (GetInputMessage)cmd.StatusMessage;
  510. if(string.Equals(m.PromptId, "keysearch.prompt", StringComparison.Ordinal))
  511. {
  512. // we're done with this chunk of the search, so we'll give the keys to the search handler.
  513. // we won't continue if we didn't find anything, even if the handler returns true
  514. bool shouldContinue = keysFound.Count != 0 && handler(keysFound.ToArray());
  515. cmd.SendLine(shouldContinue ? "N" : "Q");
  516. keysFound.Clear();
  517. break;
  518. }
  519. else goto default;
  520. default: DefaultStatusMessageHandler(cmd.StatusMessage, state); break;
  521. }
  522. }
  523. else if(line.StartsWith("pub:", StringComparison.Ordinal)) // a key description follows
  524. {
  525. string[] fields = line.Split(':');
  526. PrimaryKey key = new PrimaryKey();
  527. if(IsValidKeyId(fields[1])) key.KeyId = fields[1].ToUpperInvariant();
  528. else if(IsValidFingerprint(fields[1])) key.Fingerprint = fields[1].ToUpperInvariant();
  529. else // there's no valid ID, so skip any related records that follow
  530. {
  531. do
  532. {
  533. cmd.ReadLine(out line);
  534. if(line != null) LogLine(line);
  535. }
  536. while(line != null && !line.StartsWith("pub:", StringComparison.Ordinal));
  537. goto gotLine;
  538. }
  539. if(fields.Length > 2 && !string.IsNullOrEmpty(fields[2])) key.KeyType = ParseKeyType(fields[2]);
  540. if(fields.Length > 3 && !string.IsNullOrEmpty(fields[3])) key.Length = int.Parse(fields[3]);
  541. if(fields.Length > 4 && !string.IsNullOrEmpty(fields[4])) key.CreationTime = ParseTimestamp(fields[4]);
  542. if(fields.Length > 5 && !string.IsNullOrEmpty(fields[5])) key.ExpirationTime = ParseNullableTimestamp(fields[5]);
  543. if(fields.Length > 6 && !string.IsNullOrEmpty(fields[6]))
  544. {
  545. foreach(char c in fields[6])
  546. {
  547. switch(char.ToLowerInvariant(c))
  548. {
  549. case 'd': key.Disabled = true; break;
  550. case 'e': key.Expired = true; break;
  551. case 'r': key.Revoked = true; break;
  552. }
  553. }
  554. }
  555. // now parse the user IDs
  556. while(true)
  557. {
  558. cmd.ReadLine(out line);
  559. if(line == null) break; // if we hit a status message or EOF, break
  560. LogLine(line);
  561. if(line.StartsWith("pub:", StringComparison.Ordinal)) break;
  562. else if(!line.StartsWith("uid", StringComparison.Ordinal)) continue;
  563. fields = line.Split(':');
  564. if(string.IsNullOrEmpty(fields[1])) continue;
  565. UserId id = new UserId();
  566. id.PrimaryKey = key;
  567. id.Name = CUnescape(fields[1]);
  568. id.Signatures = NoSignatures;
  569. if(fields.Length > 2 && !string.IsNullOrEmpty(fields[2])) id.CreationTime = ParseTimestamp(fields[2]);
  570. id.MakeReadOnly();
  571. userIds.Add(id);
  572. }
  573. if(userIds.Count != 0)
  574. {
  575. key.Attributes = NoAttributes;
  576. key.DesignatedRevokers = NoRevokers;
  577. key.Signatures = NoSignatures;
  578. key.Subkeys = NoSubkeys;
  579. key.UserIds = new ReadOnlyListWrapper<UserId>(userIds.ToArray());
  580. key.MakeReadOnly();
  581. keysFound.Add(key);
  582. userIds.Clear();
  583. }
  584. goto gotLine;
  585. }
  586. }
  587. });
  588. if(!command.SuccessfulExit) throw new KeyServerFailedException("Key search failed.", commandState.FailureReasons);
  589. }
  590. /// <include file="documentation.xml" path="/Security/PGPSystem/ImportKeysFromServer/node()"/>
  591. public override ImportedKey[] ImportKeysFromServer(KeyDownloadOptions options, Keyring keyring,
  592. params string[] keyFingerprintsOrIds)
  593. {
  594. if(keyFingerprintsOrIds == null) throw new ArgumentNullException();
  595. if(keyFingerprintsOrIds.Length == 0) return new ImportedKey[0];
  596. string args = GetKeyServerArgs(options, true) + GetImportArgs(keyring, options.ImportOptions) + "--recv-keys";
  597. foreach(string id in keyFingerprintsOrIds)
  598. {
  599. if(string.IsNullOrEmpty(id)) throw new ArgumentException("A key ID was null or empty.");
  600. args += " " + id;
  601. }
  602. return KeyServerCore(args, "Key import", true, false);
  603. }
  604. /// <include file="documentation.xml" path="/Security/PGPSystem/RefreshKeyringFromServer/node()"/>
  605. public override ImportedKey[] RefreshKeysFromServer(KeyDownloadOptions options, Keyring keyring)
  606. {
  607. string args = GetImportArgs(keyring, options == null ? ImportOptions.Default : options.ImportOptions) +
  608. GetKeyServerArgs(options, false) + "--refresh-keys";
  609. return KeyServerCore(args, "Keyring refresh", true, false);
  610. }
  611. /// <include file="documentation.xml" path="/Security/PGPSystem/RefreshKeysFromServer/node()"/>
  612. public override ImportedKey[] RefreshKeysFromServer(KeyDownloadOptions options, params PrimaryKey[] keys)
  613. {
  614. if(keys == null) throw new ArgumentNullException();
  615. if(keys.Length == 0) return new ImportedKey[0];
  616. string args = GetKeyringArgs(keys, true) + GetKeyServerArgs(options, false) +
  617. GetImportArgs(null, options == null ? ImportOptions.Default : options.ImportOptions) +
  618. "--refresh-keys " + GetFingerprintArgs(keys);
  619. return KeyServerCore(args, "Key refresh", true, false);
  620. }
  621. /// <include file="documentation.xml" path="/Security/PGPSystem/UploadKeys/node()"/>
  622. public override void UploadKeys(KeyUploadOptions options, params PrimaryKey[] keys)
  623. {
  624. if(keys == null) throw new ArgumentNullException();
  625. if(keys.Length == 0) return;
  626. string args = GetKeyringArgs(keys, false) + GetKeyServerArgs(options, true) +
  627. GetExportArgs(options.ExportOptions, false, false) + "--send-keys " + GetFingerprintArgs(keys);
  628. KeyServerCore(args, "Key upload", false, true);
  629. }
  630. #endregion
  631. #region Key signing
  632. /// <include file="documentation.xml" path="/Security/PGPSystem/DeleteSignatures/node()"/>
  633. public override void DeleteSignatures(params KeySignature[] signatures)
  634. {
  635. EditSignatures(signatures, delegate(KeySignature[] sigs) { return new DeleteSigsCommand(sigs); });
  636. }
  637. /// <include file="documentation.xml" path="/Security/PGPSystem/RevokeSignatures/node()"/>
  638. public override void RevokeSignatures(UserRevocationReason reason, params KeySignature[] signatures)
  639. {
  640. EditSignatures(signatures, delegate(KeySignature[] sigs) { return new RevokeSigsCommand(reason, sigs); });
  641. }
  642. /// <include file="documentation.xml" path="/Security/PGPSystem/SignAttributes/node()"/>
  643. public override void SignAttributes(UserAttribute[] attributes, PrimaryKey signingKey, KeySigningOptions options)
  644. {
  645. if(attributes == null || signingKey == null) throw new ArgumentNullException();
  646. foreach(List<UserAttribute> attrList in GroupAttributesByKey(attributes))
  647. {
  648. EditCommand[] commands = new EditCommand[attrList.Count+1];
  649. for(int i=0; i<attrList.Count; i++) commands[i] = new RawCommand("uid " + attrList[i].Id);
  650. commands[attrList.Count] = new SignKeyCommand(options, false);
  651. PrimaryKey keyToEdit = attrList[0].PrimaryKey;
  652. DoEdit(keyToEdit, "--ask-cert-level " + GetKeyringArgs(new PrimaryKey[] { keyToEdit, signingKey }, true) +
  653. "-u " + signingKey.Fingerprint, false, commands);
  654. }
  655. }
  656. /// <include file="documentation.xml" path="/Security/PGPSystem/SignKeys/node()"/>
  657. public override void SignKeys(PrimaryKey[] keysToSign, PrimaryKey signingKey, KeySigningOptions options)
  658. {
  659. if(keysToSign == null || signingKey == null) throw new ArgumentNullException();
  660. foreach(PrimaryKey key in keysToSign)
  661. {
  662. if(key == null) throw new ArgumentException("A key was null.");
  663. DoEdit(key, "--ask-cert-level " + GetKeyringArgs(new PrimaryKey[] { key, signingKey }, true) +
  664. "-u " + signingKey.Fingerprint, false, new SignKeyCommand(options, true));
  665. }
  666. }
  667. #endregion
  668. #region Keyring queries
  669. /// <include file="documentation.xml" path="/Security/PGPSystem/FindKey/node()"/>
  670. public override PrimaryKey FindKey(string keywordOrId, Keyring keyring, ListOptions options)
  671. {
  672. PrimaryKey[] keys = FindKeys(new string[] { keywordOrId },
  673. keyring == null ? null : new Keyring[] { keyring }, keyring == null, options);
  674. return keys[0];
  675. }
  676. /// <include file="documentation.xml" path="/Security/PGPSystem/FindKeys/node()"/>
  677. public override PrimaryKey[] FindKeys(string[] fingerprintsOrIds, Keyring[] keyrings,
  678. bool includeDefaultKeyring, ListOptions options)
  679. {
  680. if(fingerprintsOrIds == null) throw new ArgumentNullException();
  681. if(fingerprintsOrIds.Length == 0) return new PrimaryKey[0];
  682. // create search arguments containing all the key IDs
  683. string searchArgs = null;
  684. if(fingerprintsOrIds.Length > 1) // if there's more than one ID, we can't allow fancy matches like email addresses,
  685. { // so validate and normalize all IDs
  686. // clone the array so we don't modify the parameters
  687. fingerprintsOrIds = (string[])fingerprintsOrIds.Clone();
  688. for(int i=0; i<fingerprintsOrIds.Length; i++)
  689. {
  690. if(string.IsNullOrEmpty(fingerprintsOrIds[i]))
  691. {
  692. throw new ArgumentException("A fingerprint/ID was null or empty.");
  693. }
  694. fingerprintsOrIds[i] = NormalizeKeyId(fingerprintsOrIds[i]);
  695. }
  696. }
  697. // add all IDs to the command line
  698. foreach(string id in fingerprintsOrIds) searchArgs += EscapeArg(id) + " ";
  699. PrimaryKey[] keys = GetKeys(keyrings, includeDefaultKeyring, options, searchArgs);
  700. if(fingerprintsOrIds.Length == 1) // if there was only a single key returned, then that's the one
  701. {
  702. return keys.Length == 1 ? keys : new PrimaryKey[1];
  703. }
  704. else
  705. {
  706. // add each key found to a dictionary
  707. Dictionary<string, PrimaryKey> keyDict = new Dictionary<string, PrimaryKey>();
  708. foreach(PrimaryKey key in keys)
  709. {
  710. keyDict[key.Fingerprint] = key;
  711. keyDict[key.KeyId] = key;
  712. keyDict[key.ShortKeyId] = key;
  713. }
  714. // then create the return array and return the keys found
  715. if(keys.Length != fingerprintsOrIds.Length) keys = new PrimaryKey[fingerprintsOrIds.Length];
  716. for(int i=0; i<keys.Length; i++) keyDict.TryGetValue(fingerprintsOrIds[i], out keys[i]);
  717. return keys;
  718. }
  719. }
  720. /// <include file="documentation.xml" path="/Security/PGPSystem/GetKeys/node()"/>
  721. public override PrimaryKey[] GetKeys(Keyring[] keyrings, bool includeDefaultKeyring, ListOptions options)
  722. {
  723. return GetKeys(keyrings, includeDefaultKeyring, options, null);
  724. }
  725. #endregion
  726. #region Miscellaneous
  727. /// <include file="documentation.xml" path="/Security/PGPSystem/CreateTrustDatabase/node()"/>
  728. public override void CreateTrustDatabase(string path)
  729. {
  730. // the following creates a valid, empty version 3 trust database. (see gpg-src\doc\DETAILS)
  731. using(FileStream dbFile = File.Open(path, FileMode.Create, FileAccess.Write))
  732. {
  733. dbFile.SetLength(40); // the database is 40 bytes long, but only the first 16 bytes are non-zero
  734. byte[] headerStart = new byte[] { 1, 0x67, 0x70, 0x67, 3, 3, 1, 5, 1, 0, 0, 0 };
  735. dbFile.Write(headerStart, 0, headerStart.Length);
  736. // the next four bytes are the big-endian creation timestamp in seconds since epoch
  737. dbFile.WriteBE4((int)((DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds));
  738. }
  739. }
  740. /// <include file="documentation.xml" path="/Security/PGPSystem/GenerateRandomData/node()"/>
  741. public override void GetRandomData(Randomness quality, byte[] buffer, int index, int count)
  742. {
  743. Utility.ValidateRange(buffer, index, count);
  744. if(count == 0) return;
  745. // "gpg --gen-random QUALITY COUNT" writes random COUNT bytes to standard output. QUALITY is a value from 0 to 2
  746. // representing the quality of the random number generator to use
  747. string qualityArg;
  748. if(quality == Randomness.Weak) qualityArg = "0";
  749. else if(quality == Randomness.TooStrong) qualityArg = "2";
  750. else qualityArg = "1"; // we'll default to the Strong level
  751. Command command = Execute("--gen-random " + qualityArg + " " + count.ToStringInvariant(), StatusMessages.Ignore, true, true);
  752. ProcessCommand(command, null,
  753. delegate(Command cmd, CommandState state) { count -= cmd.Process.StandardOutput.BaseStream.FullRead(buffer, 0, count); });
  754. if(count != 0) throw new PGPException("GPG didn't write enough random bytes.");
  755. command.CheckExitCode();
  756. }
  757. /// <include file="documentation.xml" path="/Security/PGPSystem/Hash/node()"/>
  758. public override byte[] Hash(Stream data, string hashAlgorithm)
  759. {
  760. if(data == null) throw new ArgumentNullException();
  761. bool customAlgorithm = false;
  762. if(hashAlgorithm == null || hashAlgorithm == HashAlgorithm.Default)
  763. {
  764. hashAlgorithm = HashAlgorithm.SHA1;
  765. }
  766. else if(hashAlgorithm.Length == 0)
  767. {
  768. throw new ArgumentException("Unspecified hash algorithm.");
  769. }
  770. else
  771. {
  772. AssertSupported(hashAlgorithm, hashes, "hash");
  773. customAlgorithm = true;
  774. }
  775. // "gpg --print-md ALGO" hashes data presented on standard input. if the algorithm is not supported, gpg exits
  776. // immediately with error code 2. otherwise, it consumes all available input, and then prints the hash in a
  777. // human-readable form, with hex digits nicely formatted into blocks and lines. we'll feed it all the input and
  778. // then read the output.
  779. List<byte> hash = new List<byte>();
  780. Command command = Execute("--print-md " + EscapeArg(hashAlgorithm), StatusMessages.Ignore, false, true);
  781. ProcessCommand(command, null,
  782. delegate(Command cmd, CommandState state)
  783. {
  784. if(WriteStreamToProcess(data, cmd.Process))
  785. {
  786. while(true)
  787. {
  788. string line = cmd.Process.StandardOutput.ReadLine();
  789. if(line == null) break;
  790. // on each line, there are some hex digits separated with whitespace. we'll read each character, but only
  791. // use characters that are valid hex digits
  792. int value = 0, chars = 0;
  793. foreach(char c in line.ToLowerInvariant())
  794. {
  795. if(IsHexDigit(c))
  796. {
  797. value = (value<<4) + GetHexValue(c);
  798. if(++chars == 2) // when two hex digits have accumulated, a byte is complete, so write it to the output
  799. {
  800. hash.Add((byte)value);
  801. chars = 0;
  802. }
  803. }
  804. }
  805. }
  806. }
  807. });
  808. if(!command.SuccessfulExit || hash.Count == 0)
  809. {
  810. throw new PGPException("Hash failed.",
  811. customAlgorithm ? FailureReason.UnsupportedAlgorithm : FailureReason.None);
  812. }
  813. return hash.ToArray();
  814. }
  815. #endregion
  816. #region Primary key management
  817. /// <include file="documentation.xml" path="/Security/PGPSystem/AddSubkey/node()"/>
  818. public override void AddSubkey(PrimaryKey key, string keyType, KeyCapabilities capabilities, int keyLength,
  819. DateTime? expiration)
  820. {
  821. // if a custom length is specified, it might be long enough to require a DSA2 key, so add the option just in case
  822. DoEdit(key, (keyLength == 0 ? null : "--enable-dsa2 ") + "--expert", true,
  823. new AddSubkeyCommand(this, keyType, capabilities, keyLength, expiration));
  824. }
  825. /// <include file="documentation.xml" path="/Security/PGPSystem/ChangeExpiration/node()"/>
  826. public override void ChangeExpiration(Key key, DateTime? expiration)
  827. {
  828. if(key == null) throw new ArgumentNullException();
  829. Subkey subkey = key as Subkey;
  830. if(subkey == null) // if it's not a subkey, we'll assume it's a primary key, and change that
  831. {
  832. DoEdit(key.GetPrimaryKey(), new ChangeExpirationCommand(expiration));
  833. }
  834. else // otherwise, first select the subkey
  835. {
  836. DoEdit(key.GetPrimaryKey(), new SelectSubkeyCommand(subkey.Fingerprint, true), new ChangeExpirationCommand(expiration));
  837. }
  838. }
  839. /// <include file="documentation.xml" path="/Security/PGPSystem/ChangePassword/node()"/>
  840. public override void ChangePassword(PrimaryKey key, SecureString password)
  841. {
  842. DoEdit(key, new ChangePasswordCommand(password));
  843. }
  844. /// <include file="documentation.xml" path="/Security/PGPSystem/CleanKeys/node()"/>
  845. public override void CleanKeys(params PrimaryKey[] keys)
  846. {
  847. RepeatedRawEditCommand(keys, "clean");
  848. }
  849. /// <include file="documentation.xml" path="/Security/PGPSystem/CreateKey/node()"/>
  850. /// <remarks>If <see cref="NewKeyOptions.Keyring"/> is set, the key will not be automatically trusted in the default
  851. /// trust database.
  852. /// </remarks>
  853. public override PrimaryKey CreateKey(NewKeyOptions options)
  854. {
  855. if(options == null) throw new ArgumentNullException();
  856. string email = Trim(options.Email), realName = Trim(options.RealName), comment = Trim(options.Comment);
  857. if(string.IsNullOrEmpty(email) && string.IsNullOrEmpty(realName))
  858. {
  859. throw new ArgumentException("At least one of NewKeyOptions.Email or NewKeyOptions.RealName must be set.");
  860. }
  861. if(ContainsControlCharacters(options.Comment + options.Email + options.RealName + options.Password))
  862. {
  863. throw new ArgumentException("The comment, email, real name, and/or password contains control characters. "+
  864. "Remove them.");
  865. }
  866. bool primaryIsDSA = string.IsNullOrEmpty(options.KeyType) || // DSA is the default primary key type
  867. string.Equals(options.KeyType, KeyType.DSA, StringComparison.OrdinalIgnoreCase);
  868. bool primaryIsRSA = string.Equals(options.KeyType, KeyType.RSA, StringComparison.OrdinalIgnoreCase);
  869. if(!primaryIsDSA && !primaryIsRSA)
  870. {
  871. throw new KeyCreationFailedException(FailureReason.UnsupportedAlgorithm,
  872. "Primary key type "+options.KeyType+" is not supported.");
  873. }
  874. // GPG supports key sizes from 1024 to 3072 (for DSA keys) or 4096 (for other keys)
  875. int maxKeyLength = primaryIsDSA ? 3072 : 4096;
  876. if(options.KeyLength != 0 && (options.KeyLength < 1024 || options.KeyLength > maxKeyLength))
  877. {
  878. throw new KeyCreationFailedException(FailureReason.None, "Key length " +
  879. options.KeyLength.ToStringInvariant() + " is not supported.");
  880. }
  881. bool subIsDSA = string.Equals(options.SubkeyType, KeyType.DSA, StringComparison.OrdinalIgnoreCase);
  882. bool subIsELG = string.IsNullOrEmpty(options.SubkeyType) || // ElGamal is the default subkey type
  883. string.Equals(options.SubkeyType, KeyType.ElGamal, StringComparison.OrdinalIgnoreCase) ||
  884. string.Equals(options.SubkeyType, "ELG-E", StringComparison.OrdinalIgnoreCase);
  885. bool subIsRSA = string.Equals(options.SubkeyType, KeyType.RSA, StringComparison.OrdinalIgnoreCase) ||
  886. string.Equals(options.SubkeyType, "RSA-E", StringComparison.OrdinalIgnoreCase) ||
  887. string.Equals(options.SubkeyType, "RSA-S", StringComparison.OrdinalIgnoreCase);
  888. bool subIsNone = string.Equals(options.SubkeyType, KeyType.None, StringComparison.OrdinalIgnoreCase);
  889. if(!subIsNone && !subIsDSA && !subIsELG && !subIsRSA)
  890. {
  891. throw new KeyCreationFailedException(FailureReason.UnsupportedAlgorithm,
  892. "Subkey type "+options.SubkeyType+" is not supported.");
  893. }
  894. KeyCapabilities primaryCapabilities = options.KeyCapabilities, subCapabilities = options.SubkeyCapabilities;
  895. if(primaryCapabilities == KeyCapabilities.Default)
  896. {
  897. primaryCapabilities = KeyCapabilities.Certify | KeyCapabilities.Sign | KeyCapabilities.Authenticate;
  898. }
  899. if(!subIsNone) // if a subkey will be created
  900. {
  901. // GPG supports key sizes from 1024 to 3072 (for DSA keys) or 4096 (for other keys)
  902. maxKeyLength = subIsDSA ? 3072 : 4096;
  903. if(options.SubkeyLength != 0 && (options.SubkeyLength < 1024 || options.SubkeyLength > maxKeyLength))
  904. {
  905. throw new KeyCreationFailedException(FailureReason.None, "Key length "+
  906. options.SubkeyLength.ToStringInvariant() + " is not supported.");
  907. }
  908. if(subCapabilities == KeyCapabilities.Default)
  909. {
  910. subCapabilities = subIsDSA ? KeyCapabilities.Sign : KeyCapabilities.Encrypt;
  911. }
  912. }
  913. int keyExpirationDays = GetExpirationDays(options.KeyExpiration);
  914. int subkeyExpirationDays = GetExpirationDays(options.SubkeyExpiration);
  915. // the options look good, so lets make the key
  916. string keyFingerprint = null, args = GetKeyringArgs(options.Keyring, true);
  917. // if we're using DSA keys greater than 1024 bits, we need to enable DSA2 support
  918. if(primaryIsDSA && options.KeyLength > 1024 || subIsDSA && options.SubkeyLength > 1024) args += "--enable-dsa2 ";
  919. Command command = Execute(args + "--batch --gen-key", StatusMessages.ReadInBackground, false);
  920. CommandState commandState = ProcessCommand(command,
  921. delegate(Command cmd, CommandState state)
  922. {
  923. cmd.StandardErrorLine += delegate(string line) { DefaultStandardErrorHandler(line, state); };
  924. cmd.StatusMessageReceived += delegate(StatusMessage msg)
  925. {
  926. if(msg.Type == StatusMessageType.KeyCreated) // when the key is created, grab its fingerprint
  927. {
  928. KeyCreatedMessage m = (KeyCreatedMessage)msg;
  929. if(m.PrimaryKeyCreated) keyFingerprint = m.Fingerprint;
  930. }
  931. else DefaultStatusMessageHandler(msg, state);
  932. };
  933. },
  934. delegate(Command cmd, CommandState state)
  935. {
  936. cmd.Process.StandardInput.WriteLine("Key-Type: " + (primaryIsDSA ? "DSA" : "RSA"));
  937. int keyLength = options.KeyLength != 0 ? options.KeyLength : primaryIsDSA ? 1024 : 2048;
  938. cmd.Process.StandardInput.WriteLine("Key-Length: " + keyLength.ToStringInvariant());
  939. cmd.Process.StandardInput.WriteLine("Key-Usage: " + GetKeyUsageString(primaryCapabilities));
  940. if(!subIsNone)
  941. {
  942. cmd.Process.StandardInput.WriteLine("Subkey-Type: " + (subIsDSA ? "DSA" : subIsELG ? "ELG-E" : "RSA"));
  943. cmd.Process.StandardInput.WriteLine("Subkey-Usage: " + GetKeyUsageString(subCapabilities));
  944. keyLength = options.SubkeyLength != 0 ? options.SubkeyLength : subIsDSA ? 1024 : 2048;
  945. cmd.Process.StandardInput.WriteLine("Subkey-Length: " + keyLength.ToStringInvariant());
  946. }
  947. if(!string.IsNullOrEmpty(realName)) cmd.Process.StandardInput.WriteLine("Name-Real: " + realName);
  948. if(!string.IsNullOrEmpty(email)) cmd.Process.StandardInput.WriteLine("Name-Email: " + email);
  949. if(!string.IsNullOrEmpty(comment)) cmd.Process.StandardInput.WriteLine("Name-Comment: " + comment);
  950. if(options.Password != null && options.Password.Length != 0)
  951. {
  952. cmd.Process.StandardInput.Write("Passphrase: ");

Large files files are truncated, but you can click here to view the full file