SVN
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.svn
|
||||||
1472
Form1.Designer.cs
generated
Normal file
1472
Form1.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
431
Form1.en.resx
Normal file
431
Form1.en.resx
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||||
|
<data name="pwCopyLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>39, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwDoneButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>555, 291</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwDoneButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwShowButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 291</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwShowButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwNotesLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 126</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwCopyPasswordButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>555, 343</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwCopyPasswordButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwCopyUsernameButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 343</value>
|
||||||
|
</data>
|
||||||
|
<data name="pwCopyUsernameButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinSearchTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>55, 6</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinSearchTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>629, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinSearchLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>6, 9</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinShowButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>515, 270</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinNoteTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 94</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinNoteTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 141</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinNoteLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 97</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinCodeLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 68</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinWebsiteTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 36</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinWebsiteTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinWebsiteLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 39</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinDoneButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>596, 270</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinCopyButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 270</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinCodeTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 65</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinCodeTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinEditButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>515, 241</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinDeleteButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>596, 241</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinAddButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 241</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinListbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>6, 35</value>
|
||||||
|
</data>
|
||||||
|
<data name="pinListbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>342, 604</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaNoteTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 119</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaNoteTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 133</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaNoteLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 122</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaWebsiteTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 36</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaWebsiteTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaWebsiteLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 39</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaDoneButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>596, 287</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaShowButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>515, 287</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaCopyButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 287</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaEditButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>596, 258</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaDeleteButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>515, 258</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaAddButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 258</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaAnswerTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 90</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaAnswerTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaQuestionTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 61</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaQuestionTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaSearchTextbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>55, 6</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaSearchTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>629, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaListbox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>6, 35</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaListbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>342, 604</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaAnswerLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 93</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaQuestionLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>354, 64</value>
|
||||||
|
</data>
|
||||||
|
<data name="qaSearchLabel.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>6, 9</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccHolderLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 18</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccHolderTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccDoneButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>555, 379</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccDoneButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccShowButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 379</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccShowButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccEditButton.Text" xml:space="preserve">
|
||||||
|
<value>Edit</value>
|
||||||
|
</data>
|
||||||
|
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||||
|
<data name="ccNotesTextbox.Multiline" type="System.Boolean, mscorlib">
|
||||||
|
<value>True</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccNotesTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 164</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccNotesLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 18</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccCvcLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 18</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccExpirationLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 18</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccNumberLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 18</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccCvcTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccExpirationTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccNumberTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccBrandLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>52, 15</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccBrandCombobox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccListbox.ItemHeight" type="System.Int32, mscorlib">
|
||||||
|
<value>15</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccListbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>342, 604</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccSearchLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>45, 15</value>
|
||||||
|
</data>
|
||||||
|
<data name="ccSearchTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>629, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoPasswordLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoPasswordTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoHeightLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoHeightTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoSpendkeyLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoSpendkeyTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoAddressLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoAddressTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoDoneButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>555, 465</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoDoneButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoShowButton.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 465</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoShowButton.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>115, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoNotesTextbox.Multiline" type="System.Boolean, mscorlib">
|
||||||
|
<value>True</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoNotesTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 164</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoNotesLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoViewkeyLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoSeedLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoNameLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoViewkeyTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoSeedTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoNameTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoCurrencyLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>74, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoCurrencyCombobox.Location" type="System.Drawing.Point, System.Drawing">
|
||||||
|
<value>434, 36</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoCurrencyCombobox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>250, 23</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoListbox.ItemHeight" type="System.Int32, mscorlib">
|
||||||
|
<value>15</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoListbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>342, 604</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoSearchLabel.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>45, 15</value>
|
||||||
|
</data>
|
||||||
|
<data name="cryptoSearchTextbox.Size" type="System.Drawing.Size, System.Drawing">
|
||||||
|
<value>629, 23</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
5246
Form1.ja-JP.resx
Normal file
5246
Form1.ja-JP.resx
Normal file
File diff suppressed because it is too large
Load Diff
4126
Form1.resx
Normal file
4126
Form1.resx
Normal file
File diff suppressed because it is too large
Load Diff
212
Managers/CreditcardManager.cs
Normal file
212
Managers/CreditcardManager.cs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
internal class CreditcardManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly byte[] mEncryptionKey;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public CreditcardManager(string databasePath, byte[] encryptionKey, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string cardnumber, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Cc
|
||||||
|
WHERE UPPER(Cardnumber) = UPPER(@cardnumber)";
|
||||||
|
com.Parameters.AddWithValue("@cardnumber", cardnumber);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCc(string brand, string cardnumber, string expiration, string cvc, string holdername, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(brand) || string.IsNullOrWhiteSpace(cardnumber) || string.IsNullOrWhiteSpace(expiration) || string.IsNullOrWhiteSpace(cvc) || string.IsNullOrWhiteSpace(holdername))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ブランド、カード番号、氏名、有効期限及び、CVCを御入力下さい。"
|
||||||
|
: "Please fill in the brand, card number, full name, expiration, and CVC.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedCvc = EncryptCvc(cvc);
|
||||||
|
|
||||||
|
if (AlreadyExists(cardnumber))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"カード「{brand}/{holdername} ({expiration})」は既に存在します。"
|
||||||
|
: $"The card '{brand}/{holdername} ({expiration})' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
INSERT INTO Cc (Brand, Cardnumber, Expiration, Cvc, Holdername, Note)
|
||||||
|
VALUES ($brand, $cardnumber, $expiration, $cvc, $holdername, $note)";
|
||||||
|
com.Parameters.AddWithValue("$brand", brand);
|
||||||
|
com.Parameters.AddWithValue("$cardnumber", cardnumber);
|
||||||
|
com.Parameters.AddWithValue("$expiration", expiration);
|
||||||
|
com.Parameters.AddWithValue("$cvc", encryptedCvc);
|
||||||
|
com.Parameters.AddWithValue("$holdername", holdername);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditCc(int id, string brand, string cardnumber, string expiration, string cvc, string holdername, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(brand) || string.IsNullOrWhiteSpace(cardnumber) || string.IsNullOrWhiteSpace(expiration) || string.IsNullOrWhiteSpace(cvc) || string.IsNullOrWhiteSpace(holdername))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ブランド、カード番号、氏名、有効期限及び、CVCを御入力下さい。"
|
||||||
|
: "Please fill in the brand, card number, full name, expiration, and CVC.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedCvc = EncryptCvc(cvc);
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Cc
|
||||||
|
SET Brand = $brand, Cardnumber = $cardnumber, Expiration = $expiration, Cvc = $cvc, Holdername = $holdername, Note = $note
|
||||||
|
WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
com.Parameters.AddWithValue("$brand", brand);
|
||||||
|
com.Parameters.AddWithValue("$cardnumber", cardnumber);
|
||||||
|
com.Parameters.AddWithValue("$expiration", expiration);
|
||||||
|
com.Parameters.AddWithValue("$cvc", encryptedCvc);
|
||||||
|
com.Parameters.AddWithValue("$holdername", holdername);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteCc(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Cc WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Brand, string Cardnumber, string Expiration, string Cvc, string Holdername, string Note)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var ccs = new List<(int, string, string, string, string, string, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Brand, Cardnumber, Expiration, Cvc, Holdername, Note FROM Cc
|
||||||
|
ORDER BY Brand ASC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Brand, Cardnumber, Expiration, Cvc, Holdername, Note FROM Cc
|
||||||
|
WHERE Brand LIKE @keyword OR Holdername LIKE @keyword OR Cardnumber LIKE @keyword
|
||||||
|
ORDER BY Brand ASC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string encryptedCvc = reader.GetString(4);
|
||||||
|
string decryptedCvc = DecryptCvc(encryptedCvc);
|
||||||
|
ccs.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
reader.GetString(2),
|
||||||
|
reader.GetString(3),
|
||||||
|
decryptedCvc,
|
||||||
|
reader.GetString(5),
|
||||||
|
reader.IsDBNull(6) ? string.Empty : reader.GetString(6)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ccs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncryptCvc(string answer)
|
||||||
|
{
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.GenerateIV();
|
||||||
|
byte[] iv = aes.IV;
|
||||||
|
|
||||||
|
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
|
||||||
|
{
|
||||||
|
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
|
||||||
|
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||||
|
byte[] result = new byte[iv.Length + encryptedBytes.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
|
||||||
|
|
||||||
|
return Convert.ToBase64String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptCvc(string encryptedAnswer)
|
||||||
|
{
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedAnswer);
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
231
Managers/CryptoManager.cs
Normal file
231
Managers/CryptoManager.cs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
internal class CryptoManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly byte[] mEncryptionKey;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public CryptoManager(string databasePath, byte[] encryptionKey, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string currency, string name, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Crypto
|
||||||
|
WHERE UPPER(Currency) = UPPER(@currency) AND UPPER(Name) = UPPER(@name)";
|
||||||
|
com.Parameters.AddWithValue("@currency", currency);
|
||||||
|
com.Parameters.AddWithValue("@name", name);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCrypto(string currency, string name, string address, string seed, string viewkey, string spendkey, string height, string password, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(currency) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(address))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "通貨、ウォレット名及び、住所を御入力下さい。"
|
||||||
|
: "Please fill in the currency, wallet name, and address.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPassword = !string.IsNullOrWhiteSpace(password) ? EncryptInfo(password) : "";
|
||||||
|
string encryptedSeed = !string.IsNullOrWhiteSpace(seed) ? EncryptInfo(seed) : "";
|
||||||
|
string encryptedViewkey = !string.IsNullOrWhiteSpace(viewkey) ? EncryptInfo(viewkey) : "";
|
||||||
|
string encryptedSpendkey = !string.IsNullOrWhiteSpace(spendkey) ? EncryptInfo(spendkey) : "";
|
||||||
|
|
||||||
|
if (AlreadyExists(currency, name))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"通貨及びウォレット名「{currency}/{name}」エントリは既に存在します。"
|
||||||
|
: $"An entry with the currency and wallet name for '{currency}/{name}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
INSERT INTO Crypto (Currency, Name, Address, Seed, Viewkey, Spendkey, Height, Password, Note)
|
||||||
|
VALUES ($currency, $name, $address, $seed, $viewkey, $spendkey, $height, $password, $note)";
|
||||||
|
com.Parameters.AddWithValue("$currency", currency);
|
||||||
|
com.Parameters.AddWithValue("$name", name);
|
||||||
|
com.Parameters.AddWithValue("$address", address);
|
||||||
|
com.Parameters.AddWithValue("$seed", string.IsNullOrEmpty(seed) ? DBNull.Value : encryptedSeed);
|
||||||
|
com.Parameters.AddWithValue("$viewkey", string.IsNullOrEmpty(viewkey) ? DBNull.Value : encryptedViewkey);
|
||||||
|
com.Parameters.AddWithValue("$spendkey", string.IsNullOrEmpty(spendkey) ? DBNull.Value : encryptedSpendkey);
|
||||||
|
com.Parameters.AddWithValue("$height", string.IsNullOrEmpty(height) ? DBNull.Value : height);
|
||||||
|
com.Parameters.AddWithValue("$password", string.IsNullOrEmpty(password) ? DBNull.Value : encryptedPassword);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditCrypto(int id, string currency, string name, string address, string seed, string viewkey, string spendkey, string height, string password, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(currency) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(address))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "通貨、ウォレット名及び、住所を御入力下さい。"
|
||||||
|
: "Please fill in the currency, wallet name, and address.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPassword = string.IsNullOrEmpty(password) ? string.Empty : EncryptInfo(password);
|
||||||
|
string encryptedSeed = string.IsNullOrEmpty(seed) ? string.Empty : EncryptInfo(seed);
|
||||||
|
string encryptedViewkey = string.IsNullOrEmpty(viewkey) ? string.Empty : EncryptInfo(viewkey);
|
||||||
|
string encryptedSpendkey = string.IsNullOrEmpty(spendkey) ? string.Empty : EncryptInfo(spendkey);
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Crypto
|
||||||
|
SET Currency = $currency, Name = $name, Address = $address, Seed = $seed, Viewkey = $viewkey, Spendkey = $spendkey, Height = $height, Password = $password, Note = $note
|
||||||
|
WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
com.Parameters.AddWithValue("$currency", currency);
|
||||||
|
com.Parameters.AddWithValue("$name", name);
|
||||||
|
com.Parameters.AddWithValue("$address", address);
|
||||||
|
com.Parameters.AddWithValue("$seed", string.IsNullOrEmpty(seed) ? DBNull.Value : encryptedSeed);
|
||||||
|
com.Parameters.AddWithValue("$viewkey", string.IsNullOrEmpty(viewkey) ? DBNull.Value : encryptedViewkey);
|
||||||
|
com.Parameters.AddWithValue("$spendkey", string.IsNullOrEmpty(spendkey) ? DBNull.Value : encryptedSpendkey);
|
||||||
|
com.Parameters.AddWithValue("$height", string.IsNullOrEmpty(height) ? DBNull.Value : height);
|
||||||
|
com.Parameters.AddWithValue("$password", string.IsNullOrEmpty(password) ? DBNull.Value : encryptedPassword);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteCrypto(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Crypto WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Currency, string Name, string Address, string Seed, string Viewkey, string Spendkey, string Height, string Password, string Note)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var cryptos = new List<(int, string, string, string, string, string, string, string, string, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Currency, Name, Address, Seed, Viewkey, Spendkey, Height, Password, Note FROM Crypto
|
||||||
|
ORDER BY Currency DESC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Currency, Name, Address, Seed, Viewkey, Spendkey, Height, Password, Note FROM Crypto
|
||||||
|
WHERE Currency LIKE @keyword OR Name LIKE @keyword
|
||||||
|
ORDER BY Currency DESC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string decryptedSeed = DecryptInfo(reader.GetString(4));
|
||||||
|
string decryptedViewkey = reader.IsDBNull(5) ? string.Empty : DecryptInfo(reader.GetString(5));
|
||||||
|
string decryptedSpendkey = reader.IsDBNull(6) ? string.Empty : DecryptInfo(reader.GetString(6));
|
||||||
|
string decryptedPassword = reader.IsDBNull(8) ? string.Empty : DecryptInfo(reader.GetString(8));
|
||||||
|
|
||||||
|
cryptos.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
reader.GetString(2),
|
||||||
|
reader.GetString(3),
|
||||||
|
decryptedSeed,
|
||||||
|
reader.IsDBNull(5) ? string.Empty : decryptedViewkey,
|
||||||
|
reader.IsDBNull(6) ? string.Empty : decryptedSpendkey,
|
||||||
|
reader.IsDBNull(7) ? string.Empty : reader.GetString(7),
|
||||||
|
reader.IsDBNull(8) ? string.Empty : decryptedPassword,
|
||||||
|
reader.IsDBNull(9) ? string.Empty : reader.GetString(9)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cryptos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncryptInfo(string answer)
|
||||||
|
{
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.GenerateIV();
|
||||||
|
byte[] iv = aes.IV;
|
||||||
|
|
||||||
|
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
|
||||||
|
{
|
||||||
|
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
|
||||||
|
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||||
|
byte[] result = new byte[iv.Length + encryptedBytes.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
|
||||||
|
|
||||||
|
return Convert.ToBase64String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptInfo(string encryptedAnswer)
|
||||||
|
{
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedAnswer);
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Managers/GeneratorManager.cs
Normal file
28
Managers/GeneratorManager.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
public class GeneratorManager
|
||||||
|
{
|
||||||
|
public GeneratorManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GeneratePassword(bool isSecure, int length)
|
||||||
|
{
|
||||||
|
const string charset_risky = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
const string charset_secure = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&'()=~-^\\|_@`[{]};:+*<>,./?";
|
||||||
|
string charset = isSecure ? charset_secure : charset_risky;
|
||||||
|
|
||||||
|
byte[] random = RandomNumberGenerator.GetBytes(length);
|
||||||
|
char[] res = new char[length];
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
res[i] = charset[random[i] % charset.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
Managers/NoteManager.cs
Normal file
172
Managers/NoteManager.cs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
public class NoteManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public NoteManager(string databasePath, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string name, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Notes
|
||||||
|
WHERE UPPER(Name) = UPPER(@name)";
|
||||||
|
com.Parameters.AddWithValue("@name", name);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddNote(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ファイル名を御入力下さい。"
|
||||||
|
: "Please fill in the filename.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlreadyExists(name))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ファイル名「{name}」付きメモは既に存在します。"
|
||||||
|
: $"A note with the filename '{name}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
INSERT INTO Notes (Name)
|
||||||
|
VALUES ($name)";
|
||||||
|
com.Parameters.AddWithValue("$name", name);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditNote(int id, string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ファイル名を御入力下さい。" :
|
||||||
|
"Please fill in the filename.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlreadyExists(name, id))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ファイル名「{name}」付きメモは既に存在します。"
|
||||||
|
: $"A note with the filename '{name}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Notes
|
||||||
|
SET Name = $name
|
||||||
|
WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("id", id);
|
||||||
|
com.Parameters.AddWithValue("$name", name);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveNote(int id, string text)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Notes
|
||||||
|
SET Text = @text
|
||||||
|
WHERE Id = @id";
|
||||||
|
com.Parameters.AddWithValue("@id", id);
|
||||||
|
com.Parameters.AddWithValue("@text", text ?? (object)DBNull.Value);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteNote(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Notes WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Name, string Text)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var notes = new List<(int, string, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Name, Text FROM Notes
|
||||||
|
ORDER BY Name ASC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Name, Text FROM Notes
|
||||||
|
WHERE Name LIKE @keyword
|
||||||
|
ORDER BY Name ASC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
notes.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
reader.IsDBNull(2) ? string.Empty : reader.GetString(2)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
479
Managers/OtpManager.cs
Normal file
479
Managers/OtpManager.cs
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
public class OtpManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly byte[] mEncryptionKey;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public OtpManager(string databasePath, byte[] encryptionKey, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string website, string secret, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Otp
|
||||||
|
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Secret) = UPPER(@secret)";
|
||||||
|
com.Parameters.AddWithValue("@website", website);
|
||||||
|
com.Parameters.AddWithValue("@secret", secret);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOtp(string website, string secret, string issuer, string algorithm, int duration, int digits, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(secret) || string.IsNullOrWhiteSpace(issuer))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト、秘密鍵及び、発行者を御入力下さい。"
|
||||||
|
: "Please fill in the website, secret, and issuer.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedSecret = EncryptSecret(secret);
|
||||||
|
|
||||||
|
if (AlreadyExists(website, encryptedSecret))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイト「{website}」向け秘密鍵は既に存在します。"
|
||||||
|
: $"An OTP with the website and secret for '{website}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
INSERT INTO Otp (Website, Secret, Issuer, Algorithm, Duration, Digits, Note)
|
||||||
|
VALUES ($website, $secret, $issuer, $algorithm, $duration, $digits, $note)";
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$secret", encryptedSecret);
|
||||||
|
com.Parameters.AddWithValue("$issuer", issuer);
|
||||||
|
com.Parameters.AddWithValue("$algorithm", algorithm);
|
||||||
|
com.Parameters.AddWithValue("$duration", duration);
|
||||||
|
com.Parameters.AddWithValue("$digits", digits);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditOtp(int id, string website, string secret, string issuer, string algorithm, int duration, int digits, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(secret) || string.IsNullOrWhiteSpace(issuer))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト、秘密鍵及び、発行者を御入力下さい。"
|
||||||
|
: "Please fill in the website, secret, and issuer.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedSecret = EncryptSecret(secret);
|
||||||
|
|
||||||
|
if (AlreadyExists(website, encryptedSecret, id))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイト「{website}」向け秘密鍵は既に存在します。"
|
||||||
|
: $"An OTP with the website and secret for '{website}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Otp
|
||||||
|
SET Website = $website, Secret = $secret, Issuer = $issuer, Algorithm = $algorithm, Duration = $duration, Digits = $digits, Note = $note
|
||||||
|
WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$secret", encryptedSecret);
|
||||||
|
com.Parameters.AddWithValue("$issuer", issuer);
|
||||||
|
com.Parameters.AddWithValue("$algorithm", algorithm);
|
||||||
|
com.Parameters.AddWithValue("$duration", duration);
|
||||||
|
com.Parameters.AddWithValue("$digits", digits);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteOtp(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Otp WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Website, string Secret, string Issuer, string Algorithm, int Duration, int Digits, string Note)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var otps = new List<(int, string, string, string, string, int, int, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Secret, Issuer, Algorithm, Duration, Digits, Note FROM Otp
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Secret, Issuer, Algorithm, Duration, Digits, Note FROM Otp
|
||||||
|
WHERE Website LIKE @keyword OR Issuer LIKE @keyword
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string encryptedSecret = reader.GetString(2);
|
||||||
|
string decryptedSecret = DecryptSecret(encryptedSecret);
|
||||||
|
otps.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
decryptedSecret,
|
||||||
|
reader.GetString(3),
|
||||||
|
reader.GetString(4),
|
||||||
|
reader.GetInt32(5),
|
||||||
|
reader.GetInt32(6),
|
||||||
|
reader.IsDBNull(7) ? string.Empty : reader.GetString(7)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return otps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (string Code, string Error) GenerateTotp(string secret, int digits, string algorithm, int duration = 30)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (sec, error) = ExtractSecret(secret);
|
||||||
|
if (!string.IsNullOrEmpty(error))
|
||||||
|
{
|
||||||
|
return ("", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
ulong counter = (ulong)(currentTime / duration);
|
||||||
|
uint totp = GenerateTotpCode(sec, counter, digits, algorithm);
|
||||||
|
return (totp.ToString($"D{digits}"), "");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ("", $"TOTP generation error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (byte[]? Secret, string Error) ExtractSecret(string otpauthUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(otpauthUrl))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "OTP Authを御入力下さい。"
|
||||||
|
: "Please fill in the OTP Auth.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!otpauthUrl.StartsWith("otpauth://totp/", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] secretDecoded = Base32Decode(otpauthUrl);
|
||||||
|
return (secretDecoded, "");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"TOTP秘密鍵を復号化に失敗:{ex.Message}"
|
||||||
|
: $"Failed to decode TOTP secret: {ex.Message}";
|
||||||
|
return (null, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int secretStart = otpauthUrl.IndexOf("secret=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (secretStart == -1)
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "TOTP秘密鍵をURLの中に見つけられませんでした。"
|
||||||
|
: "TOTP secret not found in URL";
|
||||||
|
return (null, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
secretStart += 7;
|
||||||
|
|
||||||
|
int secretEnd = otpauthUrl.IndexOf('&', secretStart);
|
||||||
|
if (secretEnd == -1)
|
||||||
|
{
|
||||||
|
secretEnd = otpauthUrl.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretEnd <= secretStart)
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "不正なTOTP秘密枠"
|
||||||
|
: "Invalid TOTP secret range";
|
||||||
|
return (null, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string secretEncoded = otpauthUrl.Substring(secretStart, secretEnd - secretStart);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] secretDecoded = Base32Decode(secretEncoded);
|
||||||
|
return (secretDecoded, "");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"TOTP秘密鍵を復号化に失敗:{ex.Message}"
|
||||||
|
: $"Failed to decode TOTP secret: {ex.Message}";
|
||||||
|
return (null, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (string Secret, string Issuer, string Algorithm, int Duration, int Digits) ParseOtpAuthUrl(string otpAuthUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(otpAuthUrl) || !otpAuthUrl.StartsWith("otpauth://totp/", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "不正なOTP Auth URL"
|
||||||
|
: "Invalid OTP Auth URL";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string issuer = "";
|
||||||
|
string algorithm = "SHA1";
|
||||||
|
int duration = 30;
|
||||||
|
int digits = 6;
|
||||||
|
|
||||||
|
int labelStart = "otpauth://totp/".Length;
|
||||||
|
int labelEnd = otpAuthUrl.IndexOf('?', labelStart);
|
||||||
|
if (labelEnd != -1)
|
||||||
|
{
|
||||||
|
issuer = otpAuthUrl.Substring(labelStart, labelEnd - labelStart);
|
||||||
|
if (issuer.Contains(':'))
|
||||||
|
{
|
||||||
|
issuer = issuer.Substring(0, issuer.IndexOf(':'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int secretStart = otpAuthUrl.IndexOf("secret=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (secretStart == -1)
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "TOTP秘密鍵をURLの中に見つけられませんでした。"
|
||||||
|
: "TOTP secret not found in URL";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
secretStart += 7;
|
||||||
|
int secretEnd = otpAuthUrl.IndexOf('&', secretStart);
|
||||||
|
if (secretEnd == -1)
|
||||||
|
{
|
||||||
|
secretEnd = otpAuthUrl.Length;
|
||||||
|
}
|
||||||
|
string secret = otpAuthUrl.Substring(secretStart, secretEnd - secretStart);
|
||||||
|
|
||||||
|
var queryParams = otpAuthUrl.Substring(labelEnd + 1).Split('&').Select(p => p.Split('=')).ToDictionary(p => p[0].ToLower(), p => p.Length > 1 ? p[1] : "");
|
||||||
|
if (queryParams.ContainsKey("issuer"))
|
||||||
|
{
|
||||||
|
issuer = queryParams["issuer"];
|
||||||
|
}
|
||||||
|
if (queryParams.ContainsKey("algorithm"))
|
||||||
|
{
|
||||||
|
algorithm = queryParams["algorithm"].ToUpper();
|
||||||
|
if (algorithm != "SHA1" && algorithm != "SHA256" && algorithm != "SHA512")
|
||||||
|
{
|
||||||
|
algorithm = "SHA1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queryParams.ContainsKey("period") && int.TryParse(queryParams["period"], out int parsedPeriod))
|
||||||
|
{
|
||||||
|
duration = parsedPeriod;
|
||||||
|
}
|
||||||
|
if (queryParams.ContainsKey("digits") && int.TryParse(queryParams["digits"], out int parsedDigits))
|
||||||
|
{
|
||||||
|
digits = parsedDigits;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (secret, issuer, algorithm, duration, digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint GenerateTotpCode(byte[] secret, ulong counter, int digits, string algorithm)
|
||||||
|
{
|
||||||
|
byte[] counterBytes = BitConverter.GetBytes(counter);
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(counterBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (HMAC hmac = algorithm.ToUpper() switch
|
||||||
|
{
|
||||||
|
"SHA256" => new HMACSHA256(secret),
|
||||||
|
"SHA512" => new HMACSHA512(secret),
|
||||||
|
_ => new HMACSHA1(secret),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
byte[] hash = hmac.ComputeHash(counterBytes);
|
||||||
|
int offset = hash[hash.Length - 1] & 0x0F;
|
||||||
|
uint truncatedHash =
|
||||||
|
(uint)(hash[offset] & 0x7F) << 24 |
|
||||||
|
(uint)(hash[offset + 1] & 0xFF) << 16 |
|
||||||
|
(uint)(hash[offset + 2] & 0xFF) << 8 |
|
||||||
|
(uint)(hash[offset + 3] & 0xFF);
|
||||||
|
|
||||||
|
return truncatedHash % (uint)Math.Pow(10, digits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] Base32Decode(string base32)
|
||||||
|
{
|
||||||
|
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(base32))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "Base32文字はnullや空です。"
|
||||||
|
: "Base32 string cannot be null or empty";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove spaces, hyphens, and convert to uppercase
|
||||||
|
base32 = base32.Trim().Replace(" ", "").Replace("-", "").ToUpper();
|
||||||
|
// Count padding characters
|
||||||
|
int padding = 0;
|
||||||
|
for (int i = base32.Length - 1; i >= 0 && base32[i] == '='; i--)
|
||||||
|
{
|
||||||
|
padding++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove padding for processing
|
||||||
|
string cleanBase32 = base32.Substring(0, base32.Length - padding);
|
||||||
|
if (cleanBase32.Any(c => !alphabet.Contains(c)))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "Base32文字は不正文字が含みます。"
|
||||||
|
: "Base32 string contains invalid characters";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate output length
|
||||||
|
int byteCount = cleanBase32.Length * 5 / 8;
|
||||||
|
if (byteCount == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[byteCount];
|
||||||
|
int bufferIndex = 0;
|
||||||
|
int bits = 0;
|
||||||
|
int bitCount = 0;
|
||||||
|
|
||||||
|
foreach (char c in cleanBase32)
|
||||||
|
{
|
||||||
|
int value = alphabet.IndexOf(c);
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "Base32文字は不正文字が含みます。"
|
||||||
|
: "Base32 string contains invalid characters";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
bits = bits << 5 | value;
|
||||||
|
bitCount += 5;
|
||||||
|
|
||||||
|
if (bitCount >= 8)
|
||||||
|
{
|
||||||
|
buffer[bufferIndex++] = (byte)(bits >> bitCount - 8);
|
||||||
|
bitCount -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize to actual bytes written
|
||||||
|
if (bufferIndex < byteCount)
|
||||||
|
{
|
||||||
|
Array.Resize(ref buffer, bufferIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncryptSecret(string secret)
|
||||||
|
{
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.GenerateIV();
|
||||||
|
byte[] iv = aes.IV;
|
||||||
|
|
||||||
|
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
|
||||||
|
{
|
||||||
|
byte[] plainBytes = Encoding.UTF8.GetBytes(secret);
|
||||||
|
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||||
|
byte[] result = new byte[iv.Length + encryptedBytes.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
|
||||||
|
|
||||||
|
return Convert.ToBase64String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptSecret(string encryptedSecret)
|
||||||
|
{
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedSecret);
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
214
Managers/PasswordManager.cs
Normal file
214
Managers/PasswordManager.cs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
public class PasswordManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly byte[] mEncryptionKey;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public PasswordManager(string databasePath, byte[] encryptionKey, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string website, string username, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Passwords
|
||||||
|
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Username) = UPPER(@username)";
|
||||||
|
com.Parameters.AddWithValue("@website", website);
|
||||||
|
com.Parameters.AddWithValue("@username", username);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPassword(string website, string username, string password, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト、ユーザー名及び、パスワードを御入力下さい。"
|
||||||
|
: "Please fill in the website, user/email, and password.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlreadyExists(website, username))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイトとユーザー名「{website}/{username}」付きパスワードは既に存在します。"
|
||||||
|
: $"A password with the website and user/email of '{website}/{username}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPassword = EncryptPassword(password);
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
INSERT INTO Passwords (Website, Username, Password, Note)
|
||||||
|
VALUES ($website, $username, $password, $note)";
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$username", username);
|
||||||
|
com.Parameters.AddWithValue("$password", encryptedPassword);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditPassword(int id, string website, string username, string password, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
|
{
|
||||||
|
string err = mCulture == "ja-JP"
|
||||||
|
? "ウェブサイト、ユーザー名及び、パスワードを御入力下さい。"
|
||||||
|
: "Please fill in the website, user/email, and password.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlreadyExists(website, username, id))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイトとユーザー名「{website}/{username}」付きパスワードは既に存在します。"
|
||||||
|
: $"A password with the website and user/email of '{website}/{username}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPassword = EncryptPassword(password);
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Passwords
|
||||||
|
SET Website = $website, Username = $username, Password = $password, Note = $note
|
||||||
|
WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("id", id);
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$username", username);
|
||||||
|
com.Parameters.AddWithValue("$password", encryptedPassword);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeletePassword(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Passwords WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Website, string Username, string Password, string Note)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var passwords = new List<(int, string, string, string, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Username, Password, Note FROM Passwords
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Username, Password, Note FROM Passwords
|
||||||
|
WHERE Website LIKE @keyword OR Username LIKE @keyword
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string encryptedPassword = reader.GetString(3);
|
||||||
|
string decryptedPassword = DecryptPassword(encryptedPassword);
|
||||||
|
passwords.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
reader.GetString(2),
|
||||||
|
decryptedPassword,
|
||||||
|
reader.IsDBNull(4) ? string.Empty : reader.GetString(4)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return passwords;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncryptPassword(string plainPassword)
|
||||||
|
{
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.GenerateIV();
|
||||||
|
byte[] iv = aes.IV;
|
||||||
|
|
||||||
|
using (var encryptor = aes.CreateEncryptor(aes.Key, iv))
|
||||||
|
{
|
||||||
|
byte[] plainBytes = Encoding.UTF8.GetBytes(plainPassword);
|
||||||
|
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||||
|
// Combine IV and encrypted data for storage
|
||||||
|
byte[] result = new byte[iv.Length + encryptedBytes.Length];
|
||||||
|
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
|
||||||
|
return Convert.ToBase64String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptPassword(string encryptedPassword)
|
||||||
|
{
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedPassword);
|
||||||
|
byte[] iv = new byte[16]; // AES IV is 16 bytes
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
208
Managers/PinManager.cs
Normal file
208
Managers/PinManager.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
public class PinManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly byte[] mEncryptionKey;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public PinManager(string databasePath, byte[] encryptionKey, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string website, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Pin
|
||||||
|
WHERE UPPER(Website) = UPPER(@website)";
|
||||||
|
com.Parameters.AddWithValue("@website", website);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPin(string website, string pincode, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(pincode))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト及び暗証番号を御入力下さい。"
|
||||||
|
: "Please fill in the website and pincode.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlreadyExists(website))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイト「{website}」付き暗証番号は既に存在します。"
|
||||||
|
: $"A pincode with the website '{website}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPin = EncryptPin(pincode);
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"INSERT INTO Pin (Website, Pincode, Note) VALUES ($website, $pincode, $note)";
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$pincode", encryptedPin);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditPin(int id, string website, string pincode, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(pincode))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト及び暗証番号を御入力下さい。"
|
||||||
|
: "Please fill in the website and pincode.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AlreadyExists(website, id))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイト「{website}」付き暗証番号は既に存在します。"
|
||||||
|
: $"A pincode with the website '{website}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPin = EncryptPin(pincode);
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"UPDATE Pin SET Website = $website, Pincode = $pincode, Note = $note WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("id", id);
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$pincode", encryptedPin);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeletePin(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Pin WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Website, string Pincode, string Note)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var pins = new List<(int, string, string, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Pincode, Note FROM Pin
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Pincode, Note FROM Pin
|
||||||
|
WHERE Website LIKE @keyword
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string encryptedPin = reader.GetString(2);
|
||||||
|
string decryptedPin = DecryptPin(encryptedPin);
|
||||||
|
pins.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
decryptedPin,
|
||||||
|
reader.IsDBNull(3) ? string.Empty : reader.GetString(3)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pins;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncryptPin(string plainPassword)
|
||||||
|
{
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.GenerateIV();
|
||||||
|
byte[] iv = aes.IV;
|
||||||
|
|
||||||
|
using (var encryptor = aes.CreateEncryptor(aes.Key, iv))
|
||||||
|
{
|
||||||
|
byte[] plainBytes = Encoding.UTF8.GetBytes(plainPassword);
|
||||||
|
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||||
|
byte[] result = new byte[iv.Length + encryptedBytes.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
|
||||||
|
|
||||||
|
return Convert.ToBase64String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptPin(string encryptedPassword)
|
||||||
|
{
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedPassword);
|
||||||
|
byte[] iv = new byte[16]; // AES IV is 16 bytes
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
215
Managers/QaManager.cs
Normal file
215
Managers/QaManager.cs
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows.Managers
|
||||||
|
{
|
||||||
|
public class QaManager
|
||||||
|
{
|
||||||
|
private readonly string mConnectionString;
|
||||||
|
private readonly byte[] mEncryptionKey;
|
||||||
|
private readonly string mCulture;
|
||||||
|
|
||||||
|
private string mJpLang;
|
||||||
|
|
||||||
|
public QaManager(string databasePath, byte[] encryptionKey, string culture)
|
||||||
|
{
|
||||||
|
mConnectionString = $"Data Source={databasePath}";
|
||||||
|
mEncryptionKey = encryptionKey ?? throw new ArgumentNullException(nameof(encryptionKey));
|
||||||
|
mCulture = culture;
|
||||||
|
|
||||||
|
mJpLang = "ja-JP";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AlreadyExists(string website, string question, int? excludeId = null)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT COUNT(*) FROM Qa
|
||||||
|
WHERE UPPER(Website) = UPPER(@website) AND UPPER(Question) = UPPER(@question)";
|
||||||
|
com.Parameters.AddWithValue("@website", website);
|
||||||
|
com.Parameters.AddWithValue("@question", question);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
com.CommandText += " AND Id != @excludeId";
|
||||||
|
com.Parameters.AddWithValue("@excludeId", excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToInt32(com.ExecuteScalar()) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddQa(string website, string question, string answer, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト、質問及び、回答を御入力下さい。"
|
||||||
|
: "Please fill in the website, question, and answer.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedAnswer = EncryptAnswer(answer);
|
||||||
|
|
||||||
|
if (AlreadyExists(website, question))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。"
|
||||||
|
: $"An secret question with the website and question for '{website}/{question}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
INSERT INTO Qa (Website, Question, Answer, Note)
|
||||||
|
VALUES ($website, $question, $answer, $note)";
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$question", question);
|
||||||
|
com.Parameters.AddWithValue("$answer", encryptedAnswer);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EditQa(int id, string website, string question, string answer, string note)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(website) || string.IsNullOrWhiteSpace(question) || string.IsNullOrWhiteSpace(answer))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? "ウェブサイト、質問及び、回答を御入力下さい。"
|
||||||
|
: "Please fill in the website, question, and answer.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedAnswer = EncryptAnswer(answer);
|
||||||
|
|
||||||
|
if (AlreadyExists(website, question, id))
|
||||||
|
{
|
||||||
|
string err = mCulture == mJpLang
|
||||||
|
? $"ウェブサイト及び質問「{website}/{question}」向け秘密質問は既に存在します。"
|
||||||
|
: $"An secret question with the website and question for '{website}/{question}' already exists.";
|
||||||
|
throw new ArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
UPDATE Qa
|
||||||
|
SET Website = $website, Question = $question, Answer = $answer, Note = $note
|
||||||
|
WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
com.Parameters.AddWithValue("$website", website);
|
||||||
|
com.Parameters.AddWithValue("$question", question);
|
||||||
|
com.Parameters.AddWithValue("$answer", encryptedAnswer);
|
||||||
|
com.Parameters.AddWithValue("$note", string.IsNullOrEmpty(note) ? DBNull.Value : note);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteQa(int id)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "DELETE FROM Qa WHERE Id = $id";
|
||||||
|
com.Parameters.AddWithValue("$id", id);
|
||||||
|
return com.ExecuteNonQuery() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<(int Id, string Website, string Question, string Answer, string Note)> GetAll(string keyword = "")
|
||||||
|
{
|
||||||
|
var qas = new List<(int, string, string, string, string)>();
|
||||||
|
using (SqliteConnection conn = new SqliteConnection(mConnectionString))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
if (string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Question, Answer, Note FROM Qa
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
com.CommandText = @"
|
||||||
|
SELECT Id, Website, Question, Answer, Note FROM Qa
|
||||||
|
WHERE Website LIKE @keyword OR Question LIKE @keyword
|
||||||
|
ORDER BY Website ASC";
|
||||||
|
com.Parameters.AddWithValue("@keyword", $"%{keyword}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string encryptedAnswer = reader.GetString(3);
|
||||||
|
string decryptedAnswer = DecryptAnswer(encryptedAnswer);
|
||||||
|
qas.Add((
|
||||||
|
reader.GetInt32(0),
|
||||||
|
reader.GetString(1),
|
||||||
|
reader.GetString(2),
|
||||||
|
decryptedAnswer,
|
||||||
|
reader.IsDBNull(4) ? string.Empty : reader.GetString(4)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return qas;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncryptAnswer(string answer)
|
||||||
|
{
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.GenerateIV();
|
||||||
|
byte[] iv = aes.IV;
|
||||||
|
|
||||||
|
using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, iv))
|
||||||
|
{
|
||||||
|
byte[] plainBytes = Encoding.UTF8.GetBytes(answer);
|
||||||
|
byte[] encryptedBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
|
||||||
|
byte[] result = new byte[iv.Length + encryptedBytes.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(encryptedBytes, 0, result, iv.Length, encryptedBytes.Length);
|
||||||
|
|
||||||
|
return Convert.ToBase64String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptAnswer(string encryptedAnswer)
|
||||||
|
{
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedAnswer);
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = mEncryptionKey;
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
222
MasterPasswordForm.cs
Normal file
222
MasterPasswordForm.cs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Isopoh.Cryptography.Argon2;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows
|
||||||
|
{
|
||||||
|
internal class MasterPasswordForm : Form
|
||||||
|
{
|
||||||
|
private readonly string mDatabasePath;
|
||||||
|
public byte[] EncryptionKey { get; private set; }
|
||||||
|
public bool IsValid { get; private set; }
|
||||||
|
|
||||||
|
public MasterPasswordForm(string databasePath)
|
||||||
|
{
|
||||||
|
mDatabasePath = databasePath;
|
||||||
|
InitializeComponent();
|
||||||
|
this.Icon = new Icon("simpas.ico");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.Text = "マスターパスワード";
|
||||||
|
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
this.StartPosition = FormStartPosition.CenterScreen;
|
||||||
|
this.Width = 300;
|
||||||
|
this.Height = 150;
|
||||||
|
|
||||||
|
var layout = new TableLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ColumnCount = 1,
|
||||||
|
RowCount = 2,
|
||||||
|
Padding = new Padding(10)
|
||||||
|
};
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
||||||
|
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||||
|
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
||||||
|
|
||||||
|
TextBox masPasswordTextbox = new TextBox { Dock = DockStyle.Fill, UseSystemPasswordChar = true };
|
||||||
|
Button masOkButton = new Button { Text = "OK", Dock = DockStyle.Right, Width = 80 };
|
||||||
|
Button masCancelButton = new Button { Text = "キャンセル", Dock = DockStyle.Right, Width = 80 };
|
||||||
|
|
||||||
|
masOkButton.Click += (s, e) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(masPasswordTextbox.Text))
|
||||||
|
{
|
||||||
|
MessageBox.Show("マスターパスワードは必須です。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (VerifyOrSetMasterPassword(masPasswordTextbox.Text))
|
||||||
|
{
|
||||||
|
EncryptionKey = DeriveKey(masPasswordTextbox.Text);
|
||||||
|
IsValid = VerifyKey(EncryptionKey);
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageBox.Show("不正なマスターパスワード。もう一度試してみて下さい。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"パスワードエラー:{ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
masCancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
|
||||||
|
|
||||||
|
layout.Controls.Add(masPasswordTextbox, 1, 0);
|
||||||
|
|
||||||
|
FlowLayoutPanel buttonPanel = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
FlowDirection = FlowDirection.RightToLeft
|
||||||
|
};
|
||||||
|
buttonPanel.Controls.Add(masCancelButton);
|
||||||
|
buttonPanel.Controls.Add(masOkButton);
|
||||||
|
|
||||||
|
layout.Controls.Add(buttonPanel, 0, 1);
|
||||||
|
layout.SetColumnSpan(buttonPanel, 2);
|
||||||
|
|
||||||
|
this.Controls.Add(layout);
|
||||||
|
this.AcceptButton = masOkButton;
|
||||||
|
this.CancelButton = masCancelButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool VerifyOrSetMasterPassword(string password)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection($"Data Source={mDatabasePath}"))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
SqliteCommand createTable = conn.CreateCommand();
|
||||||
|
createTable.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS Config (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Key VARCHAR(255) NOT NULL,
|
||||||
|
Value TEXT
|
||||||
|
)";
|
||||||
|
createTable.ExecuteNonQuery();
|
||||||
|
|
||||||
|
SqliteCommand select = conn.CreateCommand();
|
||||||
|
select.CommandText = "SELECT Value FROM Config WHERE Key = 'MasterPasswordHash'";
|
||||||
|
using (SqliteDataReader reader = select.ExecuteReader())
|
||||||
|
{
|
||||||
|
if (reader.Read())
|
||||||
|
{
|
||||||
|
string storedHash = reader.GetString(0);
|
||||||
|
return Argon2.Verify(storedHash, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string passwordHash = Argon2.Hash(password);
|
||||||
|
SqliteCommand insert = conn.CreateCommand();
|
||||||
|
insert.CommandText = "INSERT INTO Config (Key, Value) VALUES ('MasterPasswordHash', $value)";
|
||||||
|
insert.Parameters.AddWithValue("$value", passwordHash);
|
||||||
|
insert.ExecuteNonQuery();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] DeriveKey(string password)
|
||||||
|
{
|
||||||
|
byte[] salt = GetOrCreateSalt();
|
||||||
|
using (Rfc2898DeriveBytes derive = new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256))
|
||||||
|
{
|
||||||
|
return derive.GetBytes(32); // AES-256向け32バイト
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GetOrCreateSalt()
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection($"Data Source={mDatabasePath}"))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
SqliteCommand createTable = conn.CreateCommand();
|
||||||
|
createTable.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS Config (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Key VARCHAR(255) NOT NULL,
|
||||||
|
Value TEXT
|
||||||
|
)";
|
||||||
|
createTable.ExecuteNonQuery();
|
||||||
|
|
||||||
|
SqliteCommand select = conn.CreateCommand();
|
||||||
|
select.CommandText = "SELECT Value FROM Config WHERE Key = 'Salt'";
|
||||||
|
using (SqliteDataReader reader = select.ExecuteReader())
|
||||||
|
{
|
||||||
|
if (reader.Read())
|
||||||
|
{
|
||||||
|
byte[] salt = new byte[16];
|
||||||
|
reader.GetBytes(0, 0, salt, 0, 16);
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] newSalt = RandomNumberGenerator.GetBytes(16);
|
||||||
|
SqliteCommand insert = conn.CreateCommand();
|
||||||
|
insert.CommandText = "INSERT INTO Config (Key, Value) VALUES ('Salt', $value)";
|
||||||
|
insert.Parameters.AddWithValue("$value", newSalt);
|
||||||
|
insert.ExecuteNonQuery();
|
||||||
|
|
||||||
|
return newSalt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool VerifyKey(byte[] key)
|
||||||
|
{
|
||||||
|
using (SqliteConnection conn = new SqliteConnection($"Data Source={mDatabasePath}"))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = "SELECT COUNT(*) FROM Passwords";
|
||||||
|
long count = (long)com.ExecuteScalar();
|
||||||
|
if (count == 0) return true;
|
||||||
|
|
||||||
|
com.CommandText = "SELECT Password FROM Passwords LIMIT 1";
|
||||||
|
using (SqliteDataReader reader = com.ExecuteReader())
|
||||||
|
{
|
||||||
|
if (reader.Read())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string encryptedPassword = reader.GetString(0);
|
||||||
|
byte[] combined = Convert.FromBase64String(encryptedPassword);
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
byte[] encryptedBytes = new byte[combined.Length - iv.Length];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(combined, 0, iv, 0, iv.Length);
|
||||||
|
Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = key;
|
||||||
|
aes.IV = iv;
|
||||||
|
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
|
||||||
|
{
|
||||||
|
decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
122
Program.cs
Normal file
122
Program.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace SimPas2_Windows
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static readonly string AppDataPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"076Soft", "SimPas");
|
||||||
|
private static readonly string DatabasePath = Path.Combine(AppDataPath, "SimPas.db");
|
||||||
|
|
||||||
|
[STAThread]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
//Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en");
|
||||||
|
//Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
|
||||||
|
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("ja-JP");
|
||||||
|
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("ja-JP");
|
||||||
|
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
|
||||||
|
InitializeDatabase();
|
||||||
|
ApplicationConfiguration.Initialize();
|
||||||
|
Application.Run(new SimPas2());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InitializeDatabase()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(AppDataPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(AppDataPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(DatabasePath))
|
||||||
|
{
|
||||||
|
using (var conn = new SqliteConnection($"Data Source={DatabasePath}"))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
SqliteCommand com = conn.CreateCommand();
|
||||||
|
com.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS Settings (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Key VARCHAR(255) NOT NULL,
|
||||||
|
Value TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Passwords (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Website VARCHAR(255) NOT NULL,
|
||||||
|
Username VARCHAR(255) NOT NULL,
|
||||||
|
Password VARCHAR(255) NOT NULL,
|
||||||
|
Note TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Otp (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Website VARCHAR(255) NOT NULL,
|
||||||
|
Secret VARCHAR(255) NOT NULL,
|
||||||
|
Issuer VARCHAR(255) NOT NULL,
|
||||||
|
Algorithm VARCHAR(255) NOT NULL,
|
||||||
|
Duration INTEGER NOT NULL,
|
||||||
|
Digits INTEGER NOT NULL,
|
||||||
|
Note TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Pin (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Website VARCHAR(255) NOT NULL,
|
||||||
|
Pincode VARCHAR(6) NOT NULL,
|
||||||
|
Note TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Qa (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Website VARCHAR(255) NOT NULL,
|
||||||
|
Question VARCHAR(255) NOT NULL,
|
||||||
|
Answer VARCHAR(255) NOT NULL,
|
||||||
|
Note TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Cc (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Brand VARCHAR(50) NOT NULL,
|
||||||
|
Cardnumber VARCHAR(20) NOT NULL,
|
||||||
|
Expiration VARCHAR(5) NOT NULL,
|
||||||
|
Cvc VARCHAR(4) NOT NULL,
|
||||||
|
Holdername VARCHAR(255) NOT NULL,
|
||||||
|
Note TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Crypto (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Currency VARCHAR(20) NOT NULL,
|
||||||
|
Name VARCHAR(20) NOT NULL,
|
||||||
|
Address VARCHAR(255) NOT NULL,
|
||||||
|
Seed TEXT NOT NULL,
|
||||||
|
Viewkey VARCHAR(255),
|
||||||
|
Spendkey VARCHAR(255),
|
||||||
|
Height VARCHAR(255),
|
||||||
|
Password VARCHAR(255),
|
||||||
|
Note TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS Notes (
|
||||||
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Name VARCHAR(255) NOT NULL,
|
||||||
|
Text TEXT
|
||||||
|
);
|
||||||
|
";
|
||||||
|
com.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"Failed to initialize database: {e.Message}",
|
||||||
|
"Database Error",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
SimPas2-Windows.csproj
Normal file
28
SimPas2-Windows.csproj
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<RootNamespace>SimPas2_Windows</RootNamespace>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<ApplicationIcon>simpas.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="simpas.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="simpas.ico">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
25
SimPas2-Windows.sln
Normal file
25
SimPas2-Windows.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36414.22 d17.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimPas2-Windows", "SimPas2-Windows.csproj", "{DA781B9D-51EB-4CD7-9CDA-CBD8E601AB2F}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{DA781B9D-51EB-4CD7-9CDA-CBD8E601AB2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA781B9D-51EB-4CD7-9CDA-CBD8E601AB2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DA781B9D-51EB-4CD7-9CDA-CBD8E601AB2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DA781B9D-51EB-4CD7-9CDA-CBD8E601AB2F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {B269DEAE-3AE2-43D9-A99A-D94026802BFC}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
BIN
simpas.ico
Normal file
BIN
simpas.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
Reference in New Issue
Block a user