diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
deleted file mode 100644
index 633aa54..0000000
--- a/.github/CODEOWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-.github/* @HACKERALERT
-.github/workflows/* @HACKERALERT
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 3938344..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: "gomod"
- directory: "/"
- schedule:
- interval: "daily"
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
deleted file mode 100644
index c2524a0..0000000
--- a/.github/workflows/release-build.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-name: release-build
-
-permissions:
- contents: write
-
-on:
- push:
- tags:
- - 'v[0-9].[0-9]+.[0-9]+'
-
-
-jobs:
- release-build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '>=1.24'
- check-latest: true
- cache: false
-
- - name: Install packages
- run: |
- sudo apt update
- sudo apt upgrade -y
- sudo apt autoremove -y
- sudo apt install -y gcc libgl1-mesa-dev libglu1-mesa xorg-dev libgtk-3-dev libxkbcommon-dev
-
- - name: Install dependencies
- run: |
- go mod download
- go install -v github.com/fyne-io/fyne-cross@latest
-
- - name: Build Android
- run: |
- fyne-cross android -arch=arm64
- env:
- CGO_ENABLED: 1
-
- - name: Upload artifacts
- uses: actions/upload-artifact@v4
- with:
- name: PicoGo.apk
- path: |
- fyne-cross/dist/android-arm64/PicoGo.apk
- if-no-files-found: error
- compression-level: 9
-
- - name: Generate checksums
- run: |
- echo "CHECKSUM_PICOGO_APK=$(sha256sum fyne-cross/dist-android-arm64/PicoGo.apk | cut -d ' ' -f1)" >> $GITHUB_ENV
-
- - name: Release
- uses: softprops/action-gh-release@v2
- with:
- files: |
- fyne-cross/dist/android-arm64/PicoGo.apk
- tag_name: ${{ env.VERSION }}
- make_latest: true
- append_body: true
- body: |
- **PicoGo:**
- `sha256(PicoGo.apk): ${{ env.CHECKSUM_PICOGO_APK }}`
\ No newline at end of file
diff --git a/.github/workflows/test-build-android.yml b/.github/workflows/test-build-android.yml
deleted file mode 100644
index 6f9613e..0000000
--- a/.github/workflows/test-build-android.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: test-build-android
-
-permissions:
- contents: write
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- test-build-android:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '>=1.24'
- check-latest: true
- cache: false
-
- - name: Install packages
- run: |
- sudo apt update
- sudo apt upgrade -y
- sudo apt autoremove -y
- sudo apt install -y gcc libgl1-mesa-dev libglu1-mesa xorg-dev libgtk-3-dev libxkbcommon-dev
-
- - name: Install dependencies
- run: |
- go mod download
- go install -v github.com/fyne-io/fyne-cross@latest
-
- - name: Build Android
- run: |
- fyne-cross android -arch=arm64
- env:
- CGO_ENABLED: 1
-
- - name: Upload artifacts
- uses: actions/upload-artifact@v4
- with:
- name: TEST-BUILD-DO-NOT-USE-THIS
- path: |
- fyne-cross/dist/android-arm64/*
- if-no-files-found: error
- compression-level: 9
diff --git a/.github/workflows/test-build-linux.yml b/.github/workflows/test-build-linux.yml
deleted file mode 100644
index 87f5ba9..0000000
--- a/.github/workflows/test-build-linux.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: test-build-linux
-
-permissions:
- contents: write
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- test-build-linux:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '>=1.24'
- check-latest: true
- cache: false
-
- - name: Install packages
- run: |
- sudo apt update
- sudo apt upgrade -y
- sudo apt autoremove -y
- sudo apt install -y gcc libgl1-mesa-dev libglu1-mesa xorg-dev libgtk-3-dev libxkbcommon-dev
-
- - name: Install dependencies
- run: |
- go mod download
- go install -v github.com/fyne-io/fyne-cross@latest
-
- - name: Build Linux
- run: |
- fyne-cross linux -arch=amd64
- env:
- CGO_ENABLED: 1
-
- - name: Upload artifacts
- uses: actions/upload-artifact@v4
- with:
- name: TEST-BUILD-DO-NOT-USE-THIS
- path: |
- fyne-cross/dist/linux-amd64/*
- if-no-files-found: error
- compression-level: 9
diff --git a/.github/workflows/test-build-windows.yml b/.github/workflows/test-build-windows.yml
deleted file mode 100644
index 33b81b6..0000000
--- a/.github/workflows/test-build-windows.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: test-build-windows
-
-permissions:
- contents: write
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- test-build-windows:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '>=1.24'
- check-latest: true
- cache: false
-
- - name: Install packages
- run: |
- sudo apt update
- sudo apt upgrade -y
- sudo apt autoremove -y
- sudo apt install -y gcc libgl1-mesa-dev libglu1-mesa xorg-dev libgtk-3-dev libxkbcommon-dev
-
- - name: Install dependencies
- run: |
- go mod download
- go install -v github.com/fyne-io/fyne-cross@latest
-
- - name: Build Windows
- run: |
- fyne-cross windows -arch=amd64
- env:
- CGO_ENABLED: 1
-
- - name: Upload artifacts
- uses: actions/upload-artifact@v4
- with:
- name: TEST-BUILD-DO-NOT-USE-THIS
- path: |
- fyne-cross/dist/windows-amd64/*
- if-no-files-found: error
- compression-level: 9
diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml
deleted file mode 100644
index 52cb72b..0000000
--- a/.github/workflows/test-coverage.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: test-coverage
-
-permissions:
- contents: write
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- test-coverage:
- name: Go test coverage check
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '>=1.24'
- check-latest: true
- cache: false
-
- - name: Install packages
- run: |
- sudo apt update
- sudo apt upgrade -y
- sudo apt autoremove -y
- sudo apt install -y gcc libgl1-mesa-dev libglu1-mesa xorg-dev libgtk-3-dev libxkbcommon-dev
-
- - name: Install dependencies
- run: |
- go mod download
- go install -v github.com/fyne-io/fyne-cross@latest
-
- - name: generate test coverage
- run: go test ./... -coverprofile=./cover.out -covermode=atomic -coverpkg=./... -timeout 20m
-
- - name: check test coverage
- uses: vladopajic/go-test-coverage@f080863892c102695c8066abc08aae12e3e94e1b # v2.13.1
- with:
- config: ./.testcoverage.yml
- git-token: ${{ github.ref_name == 'main' && secrets.GITHUB_TOKEN || '' }}
- git-branch: badges
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index b51c70b..0000000
--- a/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-cover.out
diff --git a/.testcoverage.yml b/.testcoverage.yml
deleted file mode 100644
index 7c0cc18..0000000
--- a/.testcoverage.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-profile: cover.out
-threshold:
- # Minimum coverage percentage required for individual files.
- file: 0
- # Minimum coverage percentage required for each package.
- package: 0
- # Minimum overall project coverage percentage required.
- total: 60
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
deleted file mode 100644
index f755b9b..0000000
--- a/AndroidManifest.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FyneApp.toml b/FyneApp.toml
deleted file mode 100644
index 1843f5b..0000000
--- a/FyneApp.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-Website = "https://github.com/Picocrypt/PicoGo"
-
-[Details]
-Icon = "icon.png"
-Name = "PicoGo"
-ID = "io.github.picocrypt.PicoGo"
-Version = "0.1.0"
-Build = 1
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index f288702..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
- If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
- Copyright (C)
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
- You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-.
-
- The GNU General Public License does not permit incorporating your program
-into proprietary programs. If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License. But first, please read
-.
diff --git a/README.md b/README.md
index c4568e5..0530f24 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-**Moved to https://github.com/njhuffman/picogo**
+No longer supported.
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 02bf650..0000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-v0.1.5
\ No newline at end of file
diff --git a/cmd/picogo/main.go b/cmd/picogo/main.go
deleted file mode 100644
index 130194a..0000000
--- a/cmd/picogo/main.go
+++ /dev/null
@@ -1,268 +0,0 @@
-package main
-
-import (
- "errors"
- "flag"
- "fmt"
- "io"
- "os"
- "strings"
-
- "github.com/jschauma/getpass"
- "github.com/picocrypt/picogo/internal/encryption"
- "github.com/schollz/progressbar/v3"
-)
-
-type args struct {
- reedSolomon bool
- paranoid bool
- deniability bool
- inFiles []string
- keyfiles []string
- keep bool
- ordered bool
- password string
- comments string
- overwrite bool
- encryptOnly bool
- decryptOnly bool
-}
-
-var (
- reedSolomonFlag *bool
- paranoidFlag *bool
- deniabilityFlag *bool
- keyfilesStrFlag *string
- keepFlag *bool
- orderedFlag *bool
- passfromFlag *string
- commentsFlag *string
- overwriteFlag *bool
- encryptOnlyFlag *bool
- decryptOnlyFlag *bool
-)
-
-func init() {
- flag.Usage = func() {
- fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] file1 [file2 ...]\n", os.Args[0])
- fmt.Fprintf(flag.CommandLine.Output(), "\nOptions:\n")
- flag.PrintDefaults()
- }
- reedSolomonFlag = flag.Bool("rs", false, "(encryption) encode with Reed-Solomon bytes")
- paranoidFlag = flag.Bool("paranoid", false, "(encryption) use paranoid mode")
- deniabilityFlag = flag.Bool("deniability", false, "(encryption) use deniability mode")
- keyfilesStrFlag = flag.String("keyfiles", "", "list of keyfiles to use. Separate list with commas (ex: keyfile1,keyfile2,keyfile3)")
- keepFlag = flag.Bool("keep", false, "(decryption) keep output even if corrupted. If not set, the output file will be deleted.")
- orderedFlag = flag.Bool("ordered", false, "(encryption) require keyfiles in given order")
- passfromFlag = flag.String("passfrom", "tty", "password source. Options can be found in getpass documentation (github.com/jschauma/getpass)")
- commentsFlag = flag.String("comments", "", "(encryption) comments to save with the file. THESE ARE NOT ENCRYPTED. Wrap in quotes.")
- overwriteFlag = flag.Bool("overwrite", false, "overwrite existing files")
- encryptOnlyFlag = flag.Bool("encrypt-only", false, "only handle files that require encryption (only processes non-.pcv files)")
- decryptOnlyFlag = flag.Bool("decrypt-only", false, "only handle files that require decryption (only processes .pcv files)")
-
-}
-
-func parseArgs() (args, error) {
- flag.Parse()
- if flag.NArg() == 0 {
- return args{}, errors.New("no file specified")
- }
-
- password, err := getpass.Getpass(*passfromFlag)
- if err != nil {
- return args{}, fmt.Errorf("reading password from %s: %w", *passfromFlag, err)
- }
- keyfiles := []string{}
- if len(*keyfilesStrFlag) > 0 {
- keyfiles = strings.Split(*keyfilesStrFlag, ",")
- }
-
- return args{
- reedSolomon: *reedSolomonFlag,
- paranoid: *paranoidFlag,
- deniability: *deniabilityFlag,
- inFiles: flag.Args(),
- keyfiles: keyfiles,
- keep: *keepFlag,
- ordered: *orderedFlag,
- password: password,
- comments: *commentsFlag,
- overwrite: *overwriteFlag,
- encryptOnly: *encryptOnlyFlag,
- decryptOnly: *decryptOnlyFlag,
- }, nil
-}
-
-func openFiles(inFile string, keyfiles []string, outFile string, overwrite bool) (*os.File, []*os.File, *os.File, error) {
- inReader, err := os.Open(inFile)
- if err != nil {
- return nil, nil, nil, fmt.Errorf("opening %s: %w", inFile, err)
- }
- keyfileReaders := []*os.File{}
- for _, keyfile := range keyfiles {
- reader, err := os.Open(keyfile)
- if err != nil {
- return nil, nil, nil, fmt.Errorf("opening %s: %w", keyfile, err)
- }
- keyfileReaders = append(keyfileReaders, reader)
- }
- _, err = os.Stat(outFile)
- if err == nil && !overwrite {
- return nil, nil, nil, fmt.Errorf("%s already exists", outFile)
- }
- outWriter, err := os.Create(outFile)
- if err != nil {
- return nil, nil, nil, fmt.Errorf("creating %s: %w", outFile, err)
- }
- return inReader, keyfileReaders, outWriter, nil
-}
-
-func asReaders(files []*os.File) []io.Reader {
- readers := make([]io.Reader, len(files))
- for i, file := range files {
- readers[i] = file
- }
- return readers
-}
-
-func encrypt(
- inFile string,
- keyfiles []string,
- settings encryption.Settings,
- password string,
- outOf [2]int,
- overwrite bool,
-) error {
- outFile := inFile + ".pcv"
- inReader, keyfileReaders, outWriter, err := openFiles(inFile, keyfiles, outFile, overwrite)
- if err != nil {
- return fmt.Errorf("opening files: %w", err)
- }
- defer func() {
- for _, reader := range append(keyfileReaders, inReader, outWriter) {
- reader.Close()
- }
- }()
-
- tmp := make([]byte, encryption.HeaderSize(settings))
- _, err = outWriter.Write(tmp)
- if err != nil {
- return fmt.Errorf("writing blank header: %w", err)
- }
-
- bar := progressbar.NewOptions(
- -1,
- progressbar.OptionShowBytes(true),
- progressbar.OptionClearOnFinish(),
- progressbar.OptionUseIECUnits(true),
- progressbar.OptionSetDescription(
- fmt.Sprintf("[%d/%d] Encrypting: %s", outOf[0]+1, outOf[1], inFile),
- ),
- )
- header, err := encryption.EncryptHeadless(inReader, password, asReaders(keyfileReaders), settings, io.MultiWriter(outWriter, bar))
- bar.Finish() // don't catch the error, fine to ignore
- if err != nil {
- return fmt.Errorf("encrypting %s: %w", inFile, err)
- }
-
- _, err = outWriter.Seek(0, 0)
- if err != nil {
- return fmt.Errorf("seeking to start of %s: %w", outFile, err)
- }
- _, err = outWriter.Write(header)
- if err != nil {
- return fmt.Errorf("writing header to %s: %w", outFile, err)
- }
- fmt.Printf("[%d/%d] Encrypted %s to %s\n", outOf[0]+1, outOf[1], inFile, outFile)
- return nil
-}
-
-func decrypt(
- inFile string,
- keyfiles []string,
- password string,
- outOf [2]int,
- keep bool,
- overwrite bool,
-) error {
- outFile := strings.TrimSuffix(inFile, ".pcv")
- inReader, keyfileReaders, outWriter, err := openFiles(inFile, keyfiles, outFile, overwrite)
- if err != nil {
- return fmt.Errorf("opening files: %w", err)
- }
- defer func() {
- for _, reader := range append(keyfileReaders, inReader, outWriter) {
- reader.Close()
- }
- }()
-
- bar := progressbar.NewOptions(
- -1,
- progressbar.OptionShowBytes(true),
- progressbar.OptionClearOnFinish(),
- progressbar.OptionUseIECUnits(true),
- progressbar.OptionSetDescription(
- fmt.Sprintf("[%d/%d] Decrypting: %s", outOf[0]+1, outOf[1], inFile),
- ),
- )
- damaged, err := encryption.Decrypt(
- password,
- asReaders(keyfileReaders),
- inReader,
- io.MultiWriter(outWriter, bar),
- false,
- )
- bar.Finish() // don't catch the error, fine to ignore
- if err != nil {
- if !keep {
- removeErr := os.Remove(outFile)
- if removeErr != nil {
- fmt.Printf("error removing %s: %s\n", outFile, removeErr)
- }
- }
- return err
- }
- if damaged {
- fmt.Printf("Warning: %s is damaged but recovered with reed-solomon bytes. Consider re-encrypting the file.\n", inFile)
- }
- fmt.Printf("[%d/%d] Decrypted %s to %s\n", outOf[0]+1, outOf[1], inFile, outFile)
- return nil
-}
-
-func main() {
- a, err := parseArgs()
- if err != nil {
- fmt.Printf("error reading args: %s\n", err)
- os.Exit(1)
- }
-
- for i, inFile := range a.inFiles {
- if strings.HasSuffix(inFile, ".pcv") {
- if a.encryptOnly {
- fmt.Printf("[%d/%d] Skipping %s (encrypt-only is set)\n", i+1, len(a.inFiles), inFile)
- continue
- }
- err := decrypt(inFile, a.keyfiles, a.password, [2]int{i, len(a.inFiles)}, a.keep, a.overwrite)
- if err != nil {
- fmt.Printf("error while decrypting %s: %s\n", inFile, err)
- os.Exit(1)
- }
- } else {
- if a.decryptOnly {
- fmt.Printf("[%d/%d] Skipping %s (decrypt-only is set)\n", i+1, len(a.inFiles), inFile)
- continue
- }
- settings := encryption.Settings{
- Comments: a.comments,
- ReedSolomon: a.reedSolomon,
- Paranoid: a.paranoid,
- Deniability: a.deniability,
- }
- err := encrypt(inFile, a.keyfiles, settings, a.password, [2]int{i, len(a.inFiles)}, a.overwrite)
- if err != nil {
- fmt.Printf("error while encrypting %s: %s\n", inFile, err)
- os.Exit(1)
- }
- }
- }
-}
diff --git a/cmd/picogo/main_test.go b/cmd/picogo/main_test.go
deleted file mode 100644
index 91b533f..0000000
--- a/cmd/picogo/main_test.go
+++ /dev/null
@@ -1,198 +0,0 @@
-package main
-
-import (
- "os"
- "testing"
-
- "github.com/picocrypt/picogo/internal/encryption"
-)
-
-func argsMatch(a, b args) bool {
- if a.reedSolomon != b.reedSolomon {
- return false
- }
- if a.paranoid != b.paranoid {
- return false
- }
- if a.deniability != b.deniability {
- return false
- }
- if len(a.keyfiles) != len(b.keyfiles) {
- return false
- }
- for i := range a.keyfiles {
- if a.keyfiles[i] != b.keyfiles[i] {
- return false
- }
- }
- if a.keep != b.keep {
- return false
- }
- if a.ordered != b.ordered {
- return false
- }
- if a.password != b.password {
- return false
- }
- if a.comments != b.comments {
- return false
- }
- if a.overwrite != b.overwrite {
- return false
- }
- if a.encryptOnly != b.encryptOnly {
- return false
- }
- if a.decryptOnly != b.decryptOnly {
- return false
- }
- if len(a.inFiles) != len(b.inFiles) {
- return false
- }
- for i := range a.inFiles {
- if a.inFiles[i] != b.inFiles[i] {
- return false
- }
- }
- return true
-}
-
-func TestParseArgs(t *testing.T) {
- tests := []struct {
- name string
- args []string
- want args
- }{
- {
- name: "basic case",
- args: []string{"-passfrom", "pass:password", "file1.txt"},
- want: args{inFiles: []string{"file1.txt"}, password: "password"},
- },
- {
- name: "all args",
- args: []string{"-rs", "-paranoid", "-deniability", "-keyfiles", "keyfile1,keyfile2", "-keep", "-ordered", "-passfrom", "pass:password", "-comments", "test comments", "-overwrite", "-encrypt-only", "-decrypt-only", "file1.txt"},
- want: args{
- reedSolomon: true,
- paranoid: true,
- deniability: true,
- keyfiles: []string{"keyfile1", "keyfile2"},
- keep: true,
- ordered: true,
- password: "password",
- comments: "test comments",
- overwrite: true,
- encryptOnly: true,
- decryptOnly: true,
- inFiles: []string{"file1.txt"},
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- os.Args = append([]string{"picogo"}, tt.args...)
- got, err := parseArgs()
- if err != nil {
- t.Errorf("parseArgs() error = %v", err)
- return
- }
- if !argsMatch(got, tt.want) {
- t.Errorf("parseArgs() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func fileExists(filename string) bool {
- _, err := os.Stat(filename)
- return !os.IsNotExist(err)
-}
-
-func deleteFile(filename string) {
- err := os.Remove(filename)
- if err != nil {
- panic(err)
- }
-}
-
-func TestDecrypt(t *testing.T) {
- encryptedFilename := "../../internal/encryption/picocrypt_samples/v1.48/base1000.pcv"
- decryptedFilename := "../../internal/encryption/picocrypt_samples/v1.48/base1000"
- err := decrypt(
- encryptedFilename,
- []string{},
- "password",
- [2]int{0, 2},
- false,
- false,
- )
- if err != nil {
- t.Errorf("decrypt() error = %v", err)
- }
- if !fileExists(decryptedFilename) {
- t.Errorf("decrypt() did not create file %s", decryptedFilename)
- }
- deleteFile(decryptedFilename)
-
- encryptedFilename = "../../internal/encryption/picocrypt_samples/v1.48/base1000_kf12.pcv"
- decryptedFilename = "../../internal/encryption/picocrypt_samples/v1.48/base1000_kf12"
- err = decrypt(
- encryptedFilename,
- []string{
- "../../internal/encryption/picocrypt_samples/keyfiles/keyfile1",
- "../../internal/encryption/picocrypt_samples/keyfiles/keyfile2",
- },
- "password",
- [2]int{1, 2},
- false,
- true,
- )
- if err != nil {
- t.Errorf("decrypt() error = %v", err)
- }
- if !fileExists(decryptedFilename) {
- t.Errorf("decrypt() did not create file %s", decryptedFilename)
- }
- deleteFile(decryptedFilename)
-}
-
-func TestEncrypt(t *testing.T) {
- encryptedFilename := "../../internal/encryption/picocrypt_samples/basefiles/base1000.pcv"
- decryptedFilename := "../../internal/encryption/picocrypt_samples/basefiles/base1000"
- err := encrypt(
- decryptedFilename,
- []string{},
- encryption.Settings{},
- "password",
- [2]int{0, 2},
- false,
- )
- if err != nil {
- t.Errorf("encrypt() error = %v", err)
- }
- if !fileExists(encryptedFilename) {
- t.Errorf("encrypt() did not create file %s", encryptedFilename)
- }
- deleteFile(encryptedFilename)
-
- encryptedFilename = "../../internal/encryption/picocrypt_samples/basefiles/base1000.pcv"
- decryptedFilename = "../../internal/encryption/picocrypt_samples/basefiles/base1000"
- err = encrypt(
- decryptedFilename,
- []string{
- "../../internal/encryption/picocrypt_samples/keyfiles/keyfile1",
- "../../internal/encryption/picocrypt_samples/keyfiles/keyfile2",
- },
- encryption.Settings{},
- "password",
- [2]int{1, 2},
- false,
- )
- if err != nil {
- t.Errorf("encrypt() error = %v", err)
- }
- if !fileExists(encryptedFilename) {
- t.Errorf("encrypt() did not create file %s", encryptedFilename)
- }
- deleteFile(encryptedFilename)
-}
diff --git a/go.mod b/go.mod
deleted file mode 100644
index cfc4380..0000000
--- a/go.mod
+++ /dev/null
@@ -1,52 +0,0 @@
-module github.com/picocrypt/picogo
-
-go 1.23.0
-
-toolchain go1.24.1
-
-require (
- fyne.io/fyne/v2 v2.6.1
- github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec
- github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7
- github.com/jschauma/getpass v0.2.3
- github.com/schollz/progressbar/v3 v3.18.0
- golang.org/x/crypto v0.40.0
-)
-
-require (
- fyne.io/systray v1.11.0 // indirect
- github.com/BurntSushi/toml v1.4.0 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/fredbi/uri v1.1.0 // indirect
- github.com/fsnotify/fsnotify v1.8.0 // indirect
- github.com/fyne-io/gl-js v0.1.0 // indirect
- github.com/fyne-io/glfw-js v0.2.0 // indirect
- github.com/fyne-io/image v0.1.1 // indirect
- github.com/fyne-io/oksvg v0.1.0 // indirect
- github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
- github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
- github.com/go-text/render v0.2.0 // indirect
- github.com/go-text/typesetting v0.3.0 // indirect
- github.com/godbus/dbus/v5 v5.1.0 // indirect
- github.com/hack-pad/go-indexeddb v0.3.2 // indirect
- github.com/hack-pad/safejs v0.1.0 // indirect
- github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
- github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
- github.com/kr/text v0.2.0 // indirect
- github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
- github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
- github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/rivo/uniseg v0.4.7 // indirect
- github.com/rymdport/portal v0.4.1 // indirect
- github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
- github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
- github.com/stretchr/testify v1.10.0 // indirect
- github.com/yuin/goldmark v1.7.8 // indirect
- golang.org/x/image v0.24.0 // indirect
- golang.org/x/net v0.41.0 // indirect
- golang.org/x/sys v0.34.0 // indirect
- golang.org/x/term v0.33.0 // indirect
- golang.org/x/text v0.27.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
-)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index a376746..0000000
--- a/go.sum
+++ /dev/null
@@ -1,100 +0,0 @@
-fyne.io/fyne/v2 v2.6.1 h1:kjPJD4/rBS9m2nHJp+npPSuaK79yj6ObMTuzR6VQ1Is=
-fyne.io/fyne/v2 v2.6.1/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
-fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
-fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
-github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
-github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
-github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec h1:/cop0/v0HxIJm1XGDgIlzNJ3e4HhM8nIUPZi5RZ/n1w=
-github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec/go.mod h1:aaFq/WMVxrU2Exl/tXbTFSXajZrqw0mgn/wi42n0fK4=
-github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7 h1:G36G2vmQAS7CVoHQrHDGAoCWll/0kPCI8Dk7mgwcJFE=
-github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7/go.mod h1:BxsgRYwUVd92aEwXnXsfXfHw8aHlD/PUyExC/wwk9oI=
-github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
-github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
-github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
-github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
-github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
-github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
-github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
-github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
-github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM=
-github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
-github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
-github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
-github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
-github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
-github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
-github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
-github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
-github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
-github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
-github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
-github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
-github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
-github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
-github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
-github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
-github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
-github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
-github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
-github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
-github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
-github.com/jschauma/getpass v0.2.3 h1:tJySlkGtHpT3JO1UBjxrLDSp9qvbb0gKRlcNslec10g=
-github.com/jschauma/getpass v0.2.3/go.mod h1:mk40NJ7cAbcRkeDKDIajUsfQB/yweyiqjf29kyMnS18=
-github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
-github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
-github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
-github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
-github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
-github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
-github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
-github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
-github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
-github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
-github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
-github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
-github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
-github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
-github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
-golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
-golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
-golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
-golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
-golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
-golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/icon.png b/icon.png
deleted file mode 100644
index acfb25d..0000000
Binary files a/icon.png and /dev/null differ
diff --git a/internal/encryption/ciphers.go b/internal/encryption/ciphers.go
deleted file mode 100644
index 7af41bb..0000000
--- a/internal/encryption/ciphers.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package encryption
-
-import (
- "crypto/cipher"
- "fmt"
- "io"
-
- "github.com/Picocrypt/serpent"
- "golang.org/x/crypto/chacha20"
- "golang.org/x/crypto/sha3"
-)
-
-const resetNonceAt = int64(60 * (1 << 30))
-
-type nonceManager interface {
- nonce(i int) ([24]byte, error)
-}
-
-type ivManager interface {
- iv(i int) ([16]byte, error)
-}
-
-type nonceIvManager struct {
- chachaNonces [][24]byte
- serpentIVs [][16]byte
- seeds *seeds
- hkdf io.Reader
-}
-
-func (nm *nonceIvManager) extendTo(i int) error {
- if len(nm.chachaNonces) == 0 {
- nm.chachaNonces = [][24]byte{nm.seeds.Nonce}
- nm.serpentIVs = [][16]byte{nm.seeds.SerpentIV}
- }
- for i >= len(nm.chachaNonces) {
- chachaNonce := [24]byte{}
- serpentIV := [16]byte{}
- _, err := io.ReadFull(nm.hkdf, chachaNonce[:])
- if err != nil {
- return err
- }
- _, err = io.ReadFull(nm.hkdf, serpentIV[:])
- if err != nil {
- return err
- }
- nm.chachaNonces = append(nm.chachaNonces, chachaNonce)
- nm.serpentIVs = append(nm.serpentIVs, serpentIV)
- }
- return nil
-}
-
-func (nm *nonceIvManager) nonce(i int) ([24]byte, error) {
- err := nm.extendTo(i)
- if err != nil {
- return [24]byte{}, err
- }
- return nm.chachaNonces[i], nil
-}
-
-func (nm *nonceIvManager) iv(i int) ([16]byte, error) {
- err := nm.extendTo(i)
- if err != nil {
- return [16]byte{}, err
- }
- return nm.serpentIVs[i], nil
-}
-
-func newNonceManager(hkdf io.Reader, seeds *seeds) *nonceIvManager {
- nm := &nonceIvManager{
- seeds: seeds,
- hkdf: hkdf,
- }
- return nm
-}
-
-type denyNonceManager struct {
- nonces [][24]byte
- header *header
-}
-
-func (dnm *denyNonceManager) extendTo(i int) error {
- if len(dnm.nonces) == 0 {
- dnm.nonces = append(dnm.nonces, dnm.header.seeds.DenyNonce)
- }
- for i >= len(dnm.nonces) {
- previous := dnm.nonces[len(dnm.nonces)-1]
- tmp := sha3.New256()
- _, err := tmp.Write(previous[:])
- if err != nil {
- return err
- }
- nonce := [24]byte{}
- copy(nonce[:], tmp.Sum(nil))
- dnm.nonces = append(dnm.nonces, nonce)
- }
- return nil
-}
-
-func (dnm *denyNonceManager) nonce(i int) ([24]byte, error) {
- err := dnm.extendTo(i)
- if err != nil {
- return [24]byte{}, err
- }
- return dnm.nonces[i], nil
-}
-
-type serpentCipher struct {
- serpentBlock cipher.Block
- cipher cipher.Stream
- ivManager ivManager
- header *header
-}
-
-func (sc *serpentCipher) reset(i int) error {
- serpentIV, err := sc.ivManager.iv(i)
- if err != nil {
- return err
- }
- sc.cipher = cipher.NewCTR(sc.serpentBlock, serpentIV[:])
- return nil
-}
-
-func (sc *serpentCipher) xor(p []byte) {
- sc.cipher.XORKeyStream(p, p)
-}
-
-type chachaCipher struct {
- cipher *chacha20.Cipher
- nonceManager nonceManager
- key []byte
-}
-
-func (cc *chachaCipher) reset(i int) error {
- nonce, err := cc.nonceManager.nonce(i)
- if err != nil {
- return err
- }
- cc.cipher, err = chacha20.NewUnauthenticatedCipher(cc.key[:], nonce[:])
- return err
-}
-
-func (cc *chachaCipher) xor(p []byte) {
- cc.cipher.XORKeyStream(p, p)
-}
-
-type xorCipher interface {
- xor(p []byte)
- reset(i int) error
-}
-
-type rotatingCipher struct {
- xorCipher
- writtenCounter int64
- resetCounter int
- initialised bool
-}
-
-func (rc *rotatingCipher) stream(p []byte) ([]byte, error) {
- if !rc.initialised {
- err := rc.xorCipher.reset(0)
- if err != nil {
- return nil, err
- }
- rc.initialised = true
- }
- i := int64(0)
- for i < int64(len(p)) {
- j := int64(len(p)) - i
- if j > (resetNonceAt - rc.writtenCounter) {
- j = resetNonceAt - rc.writtenCounter
- }
- rc.xor(p[i : i+j])
- rc.writtenCounter += j
- if rc.writtenCounter == resetNonceAt {
- rc.writtenCounter = 0
- rc.resetCounter++
- err := rc.reset(rc.resetCounter)
- if err != nil {
- return nil, err
- }
- }
- i += j
- }
- return p, nil
-}
-
-func (rc *rotatingCipher) flush() ([]byte, error) {
- return nil, nil
-}
-
-func newDeniabilityStream(password string, header *header) streamerFlusher {
- nonceManager := denyNonceManager{header: header}
- denyKey := generateDenyKey(password, header.seeds.DenySalt)
- return &rotatingCipher{
- xorCipher: &chachaCipher{
- nonceManager: &nonceManager,
- key: denyKey[:],
- },
- }
-}
-
-func newEncryptionStreams(keys keys, header *header) ([]streamerFlusher, error) {
- nonceIvManager := newNonceManager(keys.hkdf, &(header.seeds))
- chachaStream := &rotatingCipher{
- xorCipher: &chachaCipher{
- nonceManager: nonceIvManager,
- key: keys.key[:],
- },
- }
- if !header.settings.Paranoid {
- return []streamerFlusher{chachaStream}, nil
- }
- sb, err := serpent.NewCipher(keys.serpentKey[:])
- if err != nil {
- return nil, fmt.Errorf("creating serpent cipher: %w", err)
- }
- serpentStream := &rotatingCipher{
- xorCipher: &serpentCipher{
- serpentBlock: sb,
- ivManager: nonceIvManager,
- header: header,
- cipher: nil, // will be set during streaming
- },
- }
- return []streamerFlusher{chachaStream, serpentStream}, nil
-}
diff --git a/internal/encryption/consistency_test.go b/internal/encryption/consistency_test.go
deleted file mode 100644
index d1200b8..0000000
--- a/internal/encryption/consistency_test.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "crypto/rand"
- "io"
- "log"
- mrand "math/rand/v2"
- "testing"
-
- "golang.org/x/crypto/sha3"
-)
-
-func shaArgonKey(password string, salt [16]byte, iterations uint32, parallelism uint8) [32]byte {
- // faster stand-in for testing
- data := append(append(append([]byte(password), salt[:]...), byte(iterations)), byte(parallelism))
- hasher := sha3.New256()
- _, err := hasher.Write(data)
- if err != nil {
- panic(err)
- }
- key := [32]byte{}
- copy(key[:], hasher.Sum(nil))
- return key
-}
-
-func allSettings() []Settings {
- settings := []Settings{}
- for _, comments := range []string{"", "test"} {
- for _, reedSolomon := range []bool{true, false} {
- for _, paranoid := range []bool{true, false} {
- for _, orderedKf := range []bool{true, false} {
- for _, deniability := range []bool{true, false} {
- s := Settings{comments, reedSolomon, paranoid, orderedKf, deniability}
- settings = append(settings, s)
- }
- }
- }
- }
- }
- return settings
-}
-
-func getKeyfiles(settings Settings, numKeyfiles int, t *testing.T) ([]io.Reader, []io.Reader) {
- kf1, kf2 := []io.Reader{}, []io.Reader{}
- for i := 0; i < numKeyfiles; i++ {
- key1 := make([]byte, 100)
- _, err := rand.Read(key1)
- if err != nil {
- t.Fatal(err)
- }
- key2 := make([]byte, len(key1))
- copy(key2, key1)
- kf1 = append(kf1, bytes.NewBuffer(key1))
- kf2 = append(kf2, bytes.NewBuffer(key2))
- }
- if !settings.OrderedKf {
- for i := len(kf2) - 1; i > 0; i-- {
- j := mrand.IntN(i + 1)
- kf2[i], kf2[int(j)] = kf2[int(j)], kf2[i]
- }
- }
- return kf1, kf2
-}
-
-func randomPassword() string {
- password := ""
- n := mrand.IntN(100) + 1
- for i := 0; i < n; i++ {
- char := mrand.IntN(128)
- password += string(byte(char))
- }
- return password
-}
-
-func randomData(size int) ([]byte, error) {
- data := make([]byte, size)
- _, err := rand.Read(data)
- if err != nil {
- return nil, err
- }
- return data, nil
-}
-
-func buff(data []byte) *bytes.Buffer {
- d := make([]byte, len(data))
- copy(d, data)
- return bytes.NewBuffer(d)
-}
-
-func testConsistency(settings Settings, size int, numKeyfiles int, t *testing.T) {
- original, err := randomData(size)
- if err != nil {
- t.Fatal("opening file:", err)
- }
-
- password := randomPassword()
- kf1, kf2 := getKeyfiles(settings, numKeyfiles, t)
-
- headless := bytes.NewBuffer([]byte{})
- header, err := EncryptHeadless(buff(original), password, kf1, settings, headless)
- if err != nil {
- t.Fatal(err)
- }
- headlessLen := headless.Len()
- headed := bytes.NewBuffer([]byte{})
- err = PrependHeader(headless, headed, header)
- if err != nil {
- t.Fatal(err)
- }
- encryptedLen := headed.Len()
- encrypted, err := io.ReadAll(headed)
- if err != nil {
- t.Fatal(err)
- }
- decrypted := bytes.NewBuffer([]byte{})
- Decrypt(password, kf2, buff(encrypted), decrypted, false)
-
- result, err := io.ReadAll(decrypted)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(result, original) {
- log.Println("original length:", len(original))
- log.Println("headless length:", headlessLen)
- log.Println("headed length:", encryptedLen)
- log.Println("decrypted length:", len(result))
- log.Println("missing bytes:", len(original)-len(result))
- t.Fatal("decryption does not match")
- }
-
-}
-
-func TestConsistencyEmptyFile(t *testing.T) {
- // test for an empty file
- argonKey = shaArgonKey
- for _, settings := range allSettings() {
- for numKeyfiles := range 3 {
- testConsistency(settings, 0, numKeyfiles, t)
- }
- }
-}
-
-func TestConsistencySmallFile(t *testing.T) {
- // test for a file size less than readSize
- argonKey = shaArgonKey
- for _, settings := range allSettings() {
- for numKeyfiles := range 3 {
- testConsistency(settings, readSize/2, numKeyfiles, t)
- }
- }
-}
-
-func TestConsistencyLargeFile(t *testing.T) {
- // test for a file size greater than readSize
- argonKey = shaArgonKey
- for _, settings := range allSettings() {
- for numKeyfiles := range 3 {
- testConsistency(settings, readSize*2+500, numKeyfiles, t)
- }
- }
-}
-
-func TestConsistencyReadSize(t *testing.T) {
- // test for a file exactly at readSize
- argonKey = shaArgonKey
- for _, settings := range allSettings() {
- for numKeyfiles := range 3 {
- testConsistency(settings, readSize, numKeyfiles, t)
- }
- }
-}
diff --git a/internal/encryption/decrypt.go b/internal/encryption/decrypt.go
deleted file mode 100644
index 77ff2a9..0000000
--- a/internal/encryption/decrypt.go
+++ /dev/null
@@ -1,448 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "crypto/hmac"
- "errors"
- "fmt"
- "hash"
- "io"
- "regexp"
- "strconv"
- "strings"
-
- "golang.org/x/crypto/blake2b"
- "golang.org/x/crypto/sha3"
-)
-
-type damageTracker struct {
- damage bool
-}
-
-type buffer struct {
- size int
- data []byte
-}
-
-func (b *buffer) isFull() bool {
- return len(b.data) == b.size
-}
-
-func (b *buffer) add(p []byte) []byte {
- idx := len(p)
- if len(p) > b.size-len(b.data) {
- idx = b.size - len(b.data)
- }
- if idx > 0 {
- b.data = append(b.data, p[:idx]...)
- return p[idx:]
- }
- return p
-}
-
-func parseVersion(versionBytes []byte) (bool, string, error) {
- if len(versionBytes) != (versionSize * 3) {
- return false, "", fmt.Errorf("invalid version length: %d", len(versionBytes))
- }
- v := make([]byte, versionSize)
- damaged, _, err := rsDecode(v, versionBytes[:], false)
- if err != nil {
- return damaged, "", fmt.Errorf("decoding version: %w", err)
- }
- valid, err := regexp.Match(`^v1\.\d{2}`, v)
- if err != nil {
- return damaged, "", fmt.Errorf("parsing version format: %w", err)
- }
- if !valid {
- return damaged, "", nil
- }
- return damaged, string(v), nil
-}
-
-type versionStream struct {
- buff buffer
- damageTracker *damageTracker
-}
-
-func (v *versionStream) stream(p []byte) ([]byte, error) {
- if !v.buff.isFull() {
- p = v.buff.add(p)
- if v.buff.isFull() {
- // check that the version is actually good
- damaged, version, err := parseVersion(v.buff.data)
- v.damageTracker.damage = v.damageTracker.damage || damaged
- if err != nil {
- return nil, fmt.Errorf("parsing version: %w", err)
- }
- if version == "" {
- return nil, ErrHeaderCorrupted
- }
- }
- }
- return p, nil
-}
-
-func makeVersionStream(damageTracker *damageTracker) *versionStream {
- return &versionStream{
- buff: buffer{size: versionSize * 3},
- damageTracker: damageTracker,
- }
-}
-
-type deniabilityStream struct {
- password string
- buff buffer
- deny streamer
- header *header
-}
-
-func (d *deniabilityStream) stream(p []byte) ([]byte, error) {
- if !d.buff.isFull() {
- p = d.buff.add(p)
- if d.buff.isFull() {
- // Don't catch damaged flag, it is handled by the version stream
- _, version, err := parseVersion(d.buff.data[:versionSize*3])
- if err != nil {
- return nil, fmt.Errorf("parsing version: %w", err)
- }
- if version != "" {
- d.header.settings.Deniability = false
- p = append(d.buff.data, p...)
- } else {
- d.header.settings.Deniability = true
- salt := [16]byte{}
- nonce := [24]byte{}
- copy(salt[:], d.buff.data[:len(salt)])
- copy(nonce[:], d.buff.data[len(salt):])
- d.header.seeds.DenyNonce = nonce
- d.header.seeds.DenySalt = salt
- d.deny = newDeniabilityStream(d.password, d.header)
- }
- }
- }
- if d.deny != nil {
- var err error
- p, err = d.deny.stream(p)
- if err != nil {
- return nil, fmt.Errorf("denying data: %w", err)
- }
- }
- return p, nil
-}
-
-func makeHeaderDeniabilityStream(password string, header *header) *deniabilityStream {
- return &deniabilityStream{
- password: password,
- buff: buffer{size: 16 + 24}, // 16 bytes for salt, 24 bytes for nonce
- header: header,
- deny: nil, // will be set during streaming
- }
-}
-
-type flagStream struct {
- buff buffer
- header *header
- damageTracker *damageTracker
-}
-
-func (f *flagStream) stream(p []byte) ([]byte, error) {
- if !f.buff.isFull() {
- p = f.buff.add(p)
- if f.buff.isFull() {
- data := make([]byte, flagsSize)
- damaged, corrupted, err := rsDecode(data, f.buff.data, false)
- f.damageTracker.damage = f.damageTracker.damage || damaged
- if corrupted {
- return nil, ErrHeaderCorrupted
- }
- if err != nil {
- return nil, fmt.Errorf("decoding flags: %w", err)
- }
- f.header.settings.Paranoid = data[0] == 1
- f.header.usesKf = data[1] == 1
- f.header.settings.OrderedKf = data[2] == 1
- f.header.settings.ReedSolomon = data[3] == 1
- f.header.nearMiBFlag = data[4] == 1
- }
- }
- return p, nil
-}
-
-func makeFlagStream(header *header, damageTracker *damageTracker) *flagStream {
- return &flagStream{buffer{size: flagsSize * 3}, header, damageTracker}
-}
-
-type commentStream struct {
- lenBuff buffer
- commentBuff buffer
- header *header
- damageTracker *damageTracker
-}
-
-func (c *commentStream) stream(p []byte) ([]byte, error) {
- if !c.lenBuff.isFull() {
- p = c.lenBuff.add(p)
- if c.lenBuff.isFull() {
- cLenRune := make([]byte, commentSize)
- damaged, corrupted, err := rsDecode(cLenRune, c.lenBuff.data, false)
- c.damageTracker.damage = c.damageTracker.damage || damaged
- if corrupted {
- return nil, ErrHeaderCorrupted
- }
- if err != nil {
- return nil, fmt.Errorf("decoding comment length: %w", err)
- }
- cLen, err := strconv.Atoi(string(cLenRune))
- if err != nil {
- return nil, fmt.Errorf("parsing comment length: %w", ErrHeaderCorrupted)
- }
- if (cLen < 0) || (cLen > maxCommentsLength) {
- return nil, ErrHeaderCorrupted
- }
- c.commentBuff = buffer{size: cLen * 3}
- }
- }
- if c.lenBuff.isFull() && !c.commentBuff.isFull() {
- p = c.commentBuff.add(p)
- if c.commentBuff.isFull() {
- var builder strings.Builder
- for i := 0; i < len(c.commentBuff.data); i += 3 {
- value := [1]byte{}
- // Ignore corruption on comments, they are not critical
- damaged, _, err := rsDecode(value[:], c.commentBuff.data[i:i+3], false)
- c.damageTracker.damage = c.damageTracker.damage || damaged
- if err != nil {
- return nil, fmt.Errorf("decoding comment length: %w", err)
- }
- builder.WriteByte(value[0])
- }
- c.header.settings.Comments = builder.String()
- }
- }
- return p, nil
-}
-
-func makeCommentStream(header *header, damageTracker *damageTracker) *commentStream {
- return &commentStream{
- lenBuff: buffer{size: commentSize * 3},
- header: header,
- damageTracker: damageTracker,
- }
-}
-
-type sliceStream struct {
- buff buffer
- slice []byte
- damageTracker *damageTracker
-}
-
-func (s *sliceStream) stream(p []byte) ([]byte, error) {
- if !s.buff.isFull() {
- p = s.buff.add(p)
- if s.buff.isFull() {
- data := make([]byte, len(s.slice))
- damaged, corrupted, err := rsDecode(data, s.buff.data, false)
- s.damageTracker.damage = s.damageTracker.damage || damaged
- if corrupted {
- return nil, ErrHeaderCorrupted
- }
- if err != nil {
- return nil, fmt.Errorf("decoding slice: %w", err)
- }
- copy(s.slice, data)
- }
- }
- return p, nil
-}
-
-func makeSliceStream(slice []byte, damagedTracker *damageTracker) *sliceStream {
- return &sliceStream{buffer{size: len(slice) * 3}, slice, damagedTracker}
-}
-
-type headerStream struct {
- header *header
- streams []streamer
- isDone func() bool
-}
-
-func (h *headerStream) stream(p []byte) ([]byte, error) {
- return streamStack(h.streams, p)
-}
-
-func makeHeaderStream(password string, header *header, damageTracker *damageTracker) *headerStream {
- macTagStream := makeSliceStream(header.refs.macTag[:], damageTracker)
- isDone := func() bool {
- return macTagStream.buff.isFull()
- }
- streams := []streamer{
- makeHeaderDeniabilityStream(password, header),
- makeVersionStream(damageTracker),
- makeCommentStream(header, damageTracker),
- makeFlagStream(header, damageTracker),
- makeSliceStream(header.seeds.Salt[:], damageTracker),
- makeSliceStream(header.seeds.HkdfSalt[:], damageTracker),
- makeSliceStream(header.seeds.SerpentIV[:], damageTracker),
- makeSliceStream(header.seeds.Nonce[:], damageTracker),
- makeSliceStream(header.refs.keyRef[:], damageTracker),
- makeSliceStream(header.refs.keyfileRef[:], damageTracker),
- macTagStream,
- }
- return &headerStream{header, streams, isDone}
-}
-
-func getHeader(r io.Reader, password string) (header, error) {
- h := header{}
- stream := makeHeaderStream(password, &h, &damageTracker{})
- for {
- p := make([]byte, 1000) // big enough to get most headers in one read
- n, err := r.Read(p)
- eof := errors.Is(err, io.EOF)
- if err != nil && !eof {
- return header{}, fmt.Errorf("reading file: %w", err)
- }
- _, err = stream.stream(p[:n])
- if err != nil {
- return header{}, fmt.Errorf("reading header: %w", err)
- }
- if stream.isDone() {
- return h, nil
- }
- if eof {
- return header{}, ErrFileTooShort
- }
- }
-}
-
-type macStream struct {
- mac hash.Hash
- encrypting bool
- header *header
-}
-
-func (ms *macStream) stream(p []byte) ([]byte, error) {
- _, err := ms.mac.Write(p)
- if err != nil {
- return nil, err
- }
- return p, nil
-}
-
-func (ms *macStream) flush() ([]byte, error) {
- m := ms.mac.Sum(nil)
- if ms.encrypting {
- copy(ms.header.refs.macTag[:], m)
- return nil, nil
- }
- if !hmac.Equal(m, ms.header.refs.macTag[:]) {
- return nil, ErrBodyCorrupted
- }
- return nil, nil
-}
-
-func newMacStream(keys keys, header *header, encrypting bool) (*macStream, error) {
- var mac hash.Hash
- if header.settings.Paranoid {
- mac = hmac.New(sha3.New512, keys.macKey[:])
- } else {
- var err error
- mac, err = blake2b.New512(keys.macKey[:])
- if err != nil {
- return nil, err
- }
- }
- return &macStream{mac: mac, header: header, encrypting: encrypting}, nil
-}
-
-type decryptStream struct {
- password string
- keyfiles []io.Reader
- headerStream *headerStream
- bodyStreams []streamerFlusher
- damageTracker *damageTracker
-}
-
-func (ds *decryptStream) stream(p []byte) ([]byte, error) {
- p, err := ds.headerStream.stream(p)
- if err != nil {
- return nil, err
- }
- if ds.headerStream.isDone() {
- if ds.bodyStreams == nil {
- ds.bodyStreams, err = ds.makeBodyStreams()
- if err != nil {
- return nil, err
- }
- }
- return streamStack(ds.bodyStreams, p)
- }
- return p, nil
-}
-
-func (ds *decryptStream) flush() ([]byte, error) {
- if !ds.headerStream.isDone() {
- return nil, ErrFileTooShort
- }
- if ds.bodyStreams == nil {
- return nil, nil
- }
- return flushStack(ds.bodyStreams)
-}
-
-func (ds *decryptStream) makeBodyStreams() ([]streamerFlusher, error) {
- // TODO implement keyfiles
- keys, err := validateKeys(ds.headerStream.header, ds.password, ds.keyfiles)
- if err != nil {
- // TODO should I include duplicate keyfiles error here?
- return nil, err
- }
- // TODO verify that the keyRef matches the header
- streams := []streamerFlusher{}
- // TODO: add reed solomon if configured
- if ds.headerStream.header.settings.ReedSolomon {
- streams = append(streams, makeRSDecodeStream(false, ds.headerStream.header, ds.damageTracker))
- }
- macStream, err := newMacStream(keys, ds.headerStream.header, false)
- if err != nil {
- return nil, err
- }
- streams = append(streams, macStream)
- encryptionStreams, err := newEncryptionStreams(keys, ds.headerStream.header)
- if err != nil {
- return nil, err
- }
- streams = append(streams, encryptionStreams...)
- return streams, nil
-}
-
-func makeDecryptStream(password string, keyfiles []io.Reader, damageTracker *damageTracker) *decryptStream {
- header := header{}
- return &decryptStream{
- password: password,
- keyfiles: keyfiles,
- headerStream: makeHeaderStream(password, &header, damageTracker),
- damageTracker: damageTracker,
- }
-}
-
-func validateKeys(header *header, password string, keyfiles []io.Reader) (keys, error) {
- if header.usesKf && len(keyfiles) == 0 {
- return keys{}, ErrKeyfilesRequired
- }
- if !header.usesKf && len(keyfiles) > 0 {
- return keys{}, ErrKeyfilesNotRequired
- }
- keys, err := newKeys(header.settings, header.seeds, password, keyfiles)
- if err != nil && !errors.Is(err, ErrDuplicateKeyfiles) {
- return keys, err
- }
- if !bytes.Equal(keys.keyRef[:], header.refs.keyRef[:]) {
- return keys, ErrIncorrectPassword
- }
- if header.usesKf && !bytes.Equal(keys.keyfileRef[:], header.refs.keyfileRef[:]) {
- if header.settings.OrderedKf {
- return keys, ErrIncorrectOrMisorderedKeyfiles
- }
- return keys, ErrIncorrectKeyfiles
- }
- return keys, nil
-}
diff --git a/internal/encryption/docs/code_architecture.md b/internal/encryption/docs/code_architecture.md
deleted file mode 100644
index d231b5b..0000000
--- a/internal/encryption/docs/code_architecture.md
+++ /dev/null
@@ -1,274 +0,0 @@
-# Code Architecture
-
-Here are some block diagrams of how the code is structured. Not all details are shown, but these diagrams give a reasonably cohesive idea of how data is passed through and modified.
-
-# Decryption
-## Low Detail View
-
-Data is first passed through `Header Decryption` which is responsible for initializing the shared `Header` state with correct values, consuming all header-related bytes, and undoing any encryption from deniability mode. By the time data is passed out of `Header Decryption`, the `Header` object is fully initialized and ready to be used. Any damaged bytes are reported to the shared `DamageTracker`.
-
-Data then passes through `Body Decryption` which is responsible for decoding the file data and checking it for correctness. The data output by `Body Decryption` is fully decoded. Any damaged bytes are reported to the shared `DamageTracker`.
-
-```mermaid
-flowchart TB
-
- subgraph SH[Header Decryption]
- direction LR
-
- SH_S1[[Deniability Wrapper]]
- SH_S2[[Header Fields]]
-
- SH_A[/Incoming bytes/] --> SH_S1 --> SH_S2 --> SH_B[\Outgoing bytes\]
-
- end
-
- subgraph SB[Body Decryption]
- direction LR
-
- SB_A[/Incoming bytes/]
- SB_S1[[ReedSolomon]]
- SB_S2[[MAC]]
- SB_S3[[Encryption]]
- SB_SB[\Outgoing bytes\]
-
- SB_A --> SB_S1
- SB_S1 --> SB_S2
- SB_S2 --> SB_S3
- SB_S3 --> SB_SB
- end
-
- A[/Encrypted data/] --> SH --> SB --> B[/Decrypted data/]
-
- HEADER[(Header)]
- DMG[(DamageTracker)]
-```
-
-## High Detail View
-
-```mermaid
-flowchart TB
-
- subgraph SH[Header Decryption]
- direction TB
-
- subgraph SH_S1[Deniability Wrapper]
- direction LR
- SH_S1_HEADER[(Header)]
- SH_S1_A[/Incoming bytes/] --> SH_S1_B{Are the first bytes a valid version?}
- SH_S1_B --> |Yes| SH_S1_C[\Outgoing bytes\]
- SH_S1_B --> |No| SH_S1_D[Consume the first 40 bytes]
- SH_S1_D -.-> |denyMode, denyNonce, denySalt| SH_S1_HEADER
- SH_S1_D --> SH_S1_E[ChaCha20 Cipher]
- SH_S1_HEADER -.-> |denyNonce, denySalt| SH_S1_E
- SH_S1_E --> SH_S1_C
- end
-
- subgraph SH_S2[Header Fields]
- direction LR
- SH_S2_HEADER[(Header)]
- SH_S2_DMG[(DamageTracker)]
- SH_S2_A[/Incoming bytes/] --> SH_S2_B{{"Loop over fields in [version, comments, flags, salt, hkdfSalt, serpentIV, nonce, keyRef, keyfileRef, macTag]"}}
- SH_S2_B --> |field| SH_S2_C{"Are bytes for field decodable?"}
- SH_S2_C --> |No| SH_S2_D(Consume all bytes)
- SH_S2_D -.-> |damaged| SH_S2_DMG
- SH_S2_C --> |Yes| SH_S2_E{Was error correction required?}
- SH_S2_E --> |Yes| SH_S2_F[Record damage]
- SH_S2_F -.-> |damaged| SH_S2_DMG
- SH_S2_F --> SH_S2_G[Consume field bytes]
- SH_S2_E --> |No| SH_S2_G
- SH_S2_G --> SH_S2_B
- SH_S2_G -.-> |field| SH_S2_HEADER
- SH_S2_B --> |done| SH_S2_H[\Outgoing bytes\]
-
- SH_S5_I>"Note: actual implementation unrolls the loop"]
- end
-
- SH_S1 --> SH_S2
-
- end
-
- subgraph SB[Body Decryption]
- direction TB
-
- subgraph SB_S1[ReedSolomon]
- SB_S1_HEADER[(Header)]
- SB_S1_DMG[(DamageTracker)]
-
- SB_S1_A[/Incoming bytes/] --> SB_S1_B{Has Reed Solomon encoding?}
- SB_S1_B --> |Yes| SB_S1_C[Break into 136 byte chunks]
- SB_S1_C --> SB_S1_D[Consume Reed Solomon bytes]
- SB_S1_D --> SB_S1_E{Is chunk decodable?}
- SB_S1_E --> |Yes| SB_S1_F{Was error correction required?}
- SB_S1_F --> |Yes| SB_S1_G[Record damage]
- SB_S1_G -.-> |damaged| SB_S1_DMG
- SB_S1_I -.-> |damaged| SB_S1_DMG
- SB_S1_G --> SB_S1_H[\Outgoing bytes\]
- SB_S1_E --> |No| SB_S1_I[Use first bytes as best guess]
- SB_S1_I --> SB_S1_H
- SB_S1_F --> |No| SB_S1_H
- SB_S1_HEADER -.-> |ReedSolomon| SB_S1_B
- SB_S1_B --> |No| SB_S1_H
- end
-
- subgraph SB_S2[MAC]
- direction LR
- SB_S2_HEADER[(Header)]
- SB_S2_DMG[(DamageTracker)]
- SB_S2_A[/Incoming bytes/] --> SB_S2_B{Encrypted with Paranoid mode?}
- SB_S2_B --> |Yes| SB_S2_C["Record to SHA3.512 (does not modify bytes)"]
- SB_S2_B --> |No| SB_S2_D["Record to Blake2 (does not modify bytes)"]
- SB_S2_E[\Outgoing bytes\]
- SB_S2_C --> SB_S2_E
- SB_S2_D --> SB_S2_E
-
- SB_S2_F{Does final sum match macTag?}
- SB_S2_G(Do nothing)
- SB_S2_H(Report error)
- SB_S2_C -.-> |On flush| SB_S2_F
- SB_S2_D -.-> |On flush| SB_S2_F
- SB_S2_F -.-> |Yes| SB_S2_G
- SB_S2_F -.-> |No| SB_S2_H
- SB_S2_H -.-> |damaged| SB_S2_DMG
-
- SB_S2_HEADER -.-> |paranoid| SB_S2_B
- SB_S2_HEADER -.-> |macTag| SB_S2_F
- end
-
- subgraph SB_S3[Encryption]
- direction LR
- SB_S3_HEADER[(Header)]
- SB_S3_A[/Incoming bytes/]
- SB_S3_B[ChaCha20 cipher]
- SB_S3_C{Is paranoid?}
- SB_S3_D[Serpent]
- SB_S3_E[\Outgoing bytes\]
- SB_S3_A --> SB_S3_B --> SB_S3_C
- SB_S3_C --> |Yes| SB_S3_D --> SB_S3_E
- SB_S3_C --> |No| SB_S3_E
- SB_S3_HEADER -.-> |nonce, salt| SB_S3_B
- SB_S3_HEADER -.-> |paranoid| SB_S3_C
- SB_S3_HEADER -.-> |iv| SB_S3_D
- end
-
- SB_S1 --> SB_S2 --> SB_S3
-
- end
-
- A[/Data to decrypt/] --> SH --> SB --> B[/Decrypted data/]
-```
-
-# Encryption
-
-Encryption is broken into 2 steps: encode the file itself through `Encryption Stream`, then combine the `Header` bytes with the encoded body data. These steps are separated because some header data such as the `macTag` cannot be known until the file data has been fully encrypted. The header fields required for `Encryption Stream` are initialized from the passed `Settings` and randomly generated seeds.
-
-
-```mermaid
-flowchart LR
-
- A[Settings]
- B[Random Seeds]
- C[Header]
- D[[Encryption Stream]]
- E[/Input File/]
- F[header + body]
- H[Encrypted body data]
- I[\Output File\]
- J[Apply deniability if enabled]
-
- A -.-> C
- B -.-> C
- C -.-> D
- E --> D
- D --> H
- H --> |body bytes| F
- C --> |After body data is encrypted| J --> |header bytes| F
- F --> I
-```
-
-## Low Detail View
-
-Low detail view of the `Encryption Stream`. It is very similar to running the `Decryption Stream` in reverse. Data is first passed through `Primary Encryption` (ChaCha20, plus Serpent if paranoid). Then a mac is computed and saved to the header to check against when decrypting. Then Reed-Solomon bytes are added if requested. Then everything is passed through `Deniability Wrapper` (another ChaCha20) if requested. The output bytes are the full body, ready to be appended to the header bytes, which can now be computed.
-
-```mermaid
-flowchart TB
-
- subgraph SB[Body Encryption]
- direction LR
- SB_S1[[Encryption]]
- SB_S2[[MAC]]
- SB_S3[[ReedSolomon]]
- SB_S4[[Deniability Wrapper]]
- SB_S1 --> SB_S2 --> SB_S3 --> SB_S4
- end
-
- A[/Data to encrypt/] --> SB --> B[/Encrypted data/]
-```
-
-## High Detail View
-
-```mermaid
-flowchart TB
-
- subgraph SB[Body Encryption]
- direction TB
-
- subgraph SB_S1[Encryption]
- direction LR
- SB_S1_HEADER[(Header)]
- SB_S1_A[/Incoming bytes/]
- SB_S1_B[ChaCha20 cipher]
- SB_S1_C{Is paranoid?}
- SB_S1_D[Serpent]
- SB_S1_E[\Outgoing bytes\]
- SB_S1_A --> SB_S1_B --> SB_S1_C
- SB_S1_C --> |No| SB_S1_E
- SB_S1_C --> |Yes| SB_S1_D --> SB_S1_E
- SB_S1_HEADER -.-> |nonce, salt| SB_S1_B
- SB_S1_HEADER -.-> |paranoid| SB_S1_C
- SB_S1_HEADER -.-> |iv| SB_S1_D
- end
-
- subgraph SB_S2[MAC]
- direction LR
- SB_S2_HEADER[(Header)]
- SB_S2_A[/Incoming bytes/] --> SB_S2_B{Paranoid Mode?}
- SB_S2_B --> |Yes| SB_S2_C["Record to SHA3.512 (does not modify bytes)"]
- SB_S2_B --> |No| SB_S2_D["Record to Blake2 (does not modify bytes)"]
- SB_S2_E[\Outgoing bytes\]
- SB_S2_C --> SB_S2_E
- SB_S2_D --> SB_S2_E
-
- SB_S2_F(Save sum to header)
- SB_S2_C -.-> |On flush| SB_S2_F
- SB_S2_D -.-> |On flush| SB_S2_F
- SB_S2_F -.-> |macTag| SB_S2_HEADER
-
- SB_S2_HEADER -.-> |paranoid| SB_S2_B
- end
-
- subgraph SB_S3[ReedSolomon]
- SB_S3_HEADER[(Header)]
- SB_S3_A[/Incoming bytes/] --> SB_S3_B{Reed Solomon encoding?}
- SB_S3_B --> |Yes| SB_S3_C[Break into 128 byte chunks]
- SB_S3_C --> SB_S3_D[Add 8 Reed Solomon bytes per chunk]
- SB_S3_D --> SB_S3_E[\Outgoing bytes\]
- SB_S3_HEADER -.-> |ReedSolomon| SB_S3_B
- SB_S3_B --> |No| SB_S3_E
- end
-
- subgraph SB_S4[Deniability Wrapper]
- direction LR
- SH_S4_HEADER[(Header)]
- SH_S4_A[/Incoming bytes/] --> SH_S4_B{Deniability mode?}
- SH_S4_B --> |No| SH_S4_C[\Outgoing bytes\]
- SH_S4_B --> |Yes| SH_S4_D["ChaCha20 Cipher (seeded with mock header bytes)"]
- SH_S4_HEADER -.-> |denyNonce, denySalt| SH_S4_D
- SH_S4_D --> SH_S4_C
- end
-
- SB_S1 --> SB_S2 --> SB_S3 --> SB_S4
-
- end
-
- A[/Data to encrypt/] --> SB --> B[/Encrypted data/]
-```
\ No newline at end of file
diff --git a/internal/encryption/encrypt.go b/internal/encryption/encrypt.go
deleted file mode 100644
index 37bac7b..0000000
--- a/internal/encryption/encrypt.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package encryption
-
-import (
- "fmt"
- "io"
-)
-
-var picocryptVersion = "v1.48"
-
-type sizeStream struct {
- header *header
- counter int64
-}
-
-func (s *sizeStream) stream(p []byte) ([]byte, error) {
- s.counter += int64(len(p))
- return p, nil
-}
-
-func (s *sizeStream) flush() ([]byte, error) {
- s.header.nearMiBFlag = (s.counter % (1 << 20)) > ((1 << 20) - chunkSize)
- return nil, nil
-}
-
-type encryptStream struct {
- header *header
- streams []streamerFlusher
-}
-
-func (es *encryptStream) stream(p []byte) ([]byte, error) {
- return streamStack(es.streams, p)
-}
-
-func (es *encryptStream) flush() ([]byte, error) {
- return flushStack(es.streams)
-}
-
-func makeEncryptStream(settings Settings, seeds seeds, password string, keyfiles []io.Reader) (*encryptStream, error) {
- keys, err := newKeys(settings, seeds, password, keyfiles)
- if err != nil {
- return nil, fmt.Errorf("generating keys: %w", err)
- }
- header := header{
- settings: settings,
- seeds: seeds,
- refs: refs{
- keyRef: keys.keyRef,
- keyfileRef: keys.keyfileRef,
- macTag: [64]byte{}, // will be filled by mac stream
- },
- usesKf: len(keyfiles) > 0,
- nearMiBFlag: false,
- }
-
- streams := []streamerFlusher{}
-
- encryptionStreams, err := newEncryptionStreams(keys, &header)
- if err != nil {
- return nil, fmt.Errorf("creating encryption stream: %w", err)
- }
- streams = append(streams, encryptionStreams...)
-
- macStream, err := newMacStream(keys, &header, true)
- if err != nil {
- return nil, fmt.Errorf("creating mac stream: %w", err)
- }
- streams = append(streams, macStream)
-
- sizeStream := sizeStream{header: &header}
- streams = append(streams, &sizeStream)
-
- if settings.ReedSolomon {
- streams = append(streams, makeRSEncodeStream(&header))
- }
-
- if settings.Deniability {
- deniabilityStream := newDeniabilityStream(password, &header)
- mockHeaderData := make([]byte, baseHeaderSize+3*len(settings.Comments))
- _, err := deniabilityStream.stream(mockHeaderData)
- if err != nil {
- return nil, fmt.Errorf("seeding deniability stream: %w", err)
- }
- streams = append(streams, deniabilityStream)
- }
-
- return &encryptStream{
- header: &header,
- streams: streams,
- }, nil
-}
diff --git a/internal/encryption/encryption.go b/internal/encryption/encryption.go
deleted file mode 100644
index 7dbb7e5..0000000
--- a/internal/encryption/encryption.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package encryption
-
-import (
- "errors"
- "fmt"
- "io"
-)
-
-const readSize = 1 << 20
-const maxCommentsLength = 99999
-
-var ErrFileTooShort = errors.New("file too short")
-var ErrIncorrectPassword = errors.New("incorrect password")
-var ErrIncorrectKeyfiles = errors.New("incorrect keyfiles")
-var ErrIncorrectOrMisorderedKeyfiles = errors.New("incorrect or misordered keyfiles")
-var ErrKeyfilesRequired = errors.New("missing required keyfiles")
-var ErrDuplicateKeyfiles = errors.New("duplicate keyfiles")
-var ErrKeyfilesNotRequired = errors.New("keyfiles not required")
-var ErrHeaderCorrupted = errors.New("header corrupted")
-var ErrBodyCorrupted = errors.New("body corrupted")
-var ErrCommentsTooLong = errors.New("comments exceed maximum length")
-
-type Settings struct {
- Comments string
- ReedSolomon bool
- Paranoid bool
- OrderedKf bool
- Deniability bool
-}
-
-func Decrypt(
- pw string,
- kf []io.Reader,
- r io.Reader,
- w io.Writer,
- skipReedSolomon bool,
-) (bool, error) {
-
- damageTracker := damageTracker{}
- decryptStream := makeDecryptStream(pw, kf, &damageTracker)
-
- for {
- p := make([]byte, readSize)
- n, err := r.Read(p)
- eof := false
- if err != nil {
- if errors.Is(err, io.EOF) {
- eof = true
- } else {
- return false, fmt.Errorf("reading input: %w", err)
- }
- }
- p, err = decryptStream.stream(p[:n])
- if err != nil {
- return damageTracker.damage, err
- }
- _, err = w.Write(p)
- if err != nil {
- return damageTracker.damage, err
- }
- if eof {
- p, err := decryptStream.flush()
- if (err == nil) || errors.Is(err, ErrBodyCorrupted) {
- _, e := w.Write(p)
- return damageTracker.damage, e
- }
- return damageTracker.damage, err
- }
- }
-}
-
-func GetEncryptionSettings(r io.Reader) (Settings, error) {
- header, err := getHeader(r, "")
- if errors.Is(err, ErrFileTooShort) {
- return Settings{Deniability: true}, nil
- }
- if err != nil {
- return Settings{}, fmt.Errorf("reading header: %w", err)
- }
- return header.settings, nil
-}
-
-func EncryptHeadless(
- in io.Reader,
- password string,
- keyfiles []io.Reader,
- settings Settings,
- out io.Writer,
-) ([]byte, error) {
- if len(settings.Comments) > maxCommentsLength {
- return nil, ErrCommentsTooLong
- }
- seeds, err := randomSeeds()
- if err != nil {
- return nil, fmt.Errorf("generating seeds: %w", err)
- }
- return encryptWithSeeds(in, password, keyfiles, settings, out, seeds)
-}
-
-func encryptWithSeeds(
- in io.Reader,
- password string,
- keyfiles []io.Reader,
- settings Settings,
- out io.Writer,
- seeds seeds,
-) ([]byte, error) {
- encryptionStream, err := makeEncryptStream(settings, seeds, password, keyfiles)
- if err != nil {
- return nil, fmt.Errorf("making encryption stream: %w", err)
- }
-
- buf := make([]byte, readSize)
- for {
- eof := false
- n, err := in.Read(buf)
- if err != nil {
- if errors.Is(err, io.EOF) {
- eof = true
- } else {
- return nil, fmt.Errorf("reading input: %w", err)
- }
- }
- p, err := encryptionStream.stream(buf[:n])
- if err != nil {
- return nil, fmt.Errorf("encrypting input: %w", err)
- }
- _, err = out.Write(p)
- if err != nil {
- return nil, fmt.Errorf("writing output: %w", err)
- }
- if eof {
- break
- }
- }
-
- p, err := encryptionStream.flush()
- if err != nil {
- return nil, fmt.Errorf("closing encryptor: %w", err)
- }
- _, err = out.Write(p)
- if err != nil {
- return nil, fmt.Errorf("writing output: %w", err)
- }
-
- headerBytes, err := encryptionStream.header.bytes(password)
- if err != nil {
- return nil, fmt.Errorf("making header: %w", err)
- }
- return headerBytes, nil
-}
-
-func PrependHeader(
- headless io.Reader,
- headed io.Writer,
- header []byte,
-) error {
- _, err := headed.Write(header)
- if err != nil {
- return fmt.Errorf("writing header: %w", err)
- }
-
- for {
- data := make([]byte, readSize)
- n, err := headless.Read(data)
- eof := err == io.EOF
- if (err != nil) && (err != io.EOF) {
- return fmt.Errorf("reading body data: %w", err)
- }
- data = data[:n]
-
- _, err = headed.Write(data)
- if err != nil {
- return fmt.Errorf("writing body data: %w", err)
- }
-
- if eof {
- break
- }
- }
- return nil
-}
-
-func HeaderSize(settings Settings) int {
- size := baseHeaderSize + 3*len(settings.Comments)
- if settings.Deniability {
- size += len(seeds{}.DenyNonce) + len(seeds{}.DenySalt)
- }
- return size
-}
diff --git a/internal/encryption/expected_errors_test.go b/internal/encryption/expected_errors_test.go
deleted file mode 100644
index 5c20475..0000000
--- a/internal/encryption/expected_errors_test.go
+++ /dev/null
@@ -1,174 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "crypto/rand"
- "errors"
- "io"
- "os"
- "testing"
-)
-
-func TestFileTooShort(t *testing.T) {
- argonKey = argon2IDKey
- for size := range []int{0, 10} {
- invalidData := make([]byte, size)
- _, err := rand.Read(invalidData)
- if err != nil {
- t.Fatal("creating random data:", err)
- }
- _, err = Decrypt("password", []io.Reader{}, bytes.NewBuffer(invalidData), bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrFileTooShort) {
- t.Fatal("expected ErrFileTooShort, got", err)
- }
- }
-}
-
-func TestHeaderCorrupted(t *testing.T) {
- argonKey = argon2IDKey
- invalidData := make([]byte, 1000)
- _, err := rand.Read(invalidData)
- if err != nil {
- t.Fatal("creating random data:", err)
- }
- _, err = Decrypt("password", []io.Reader{}, bytes.NewBuffer(invalidData), bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrHeaderCorrupted) {
- t.Fatal("expected ErrHeaderCorrupted, got", err)
- }
-}
-
-func TestIncorrectPassword(t *testing.T) {
- argonKey = argon2IDKey
- reader, err := os.Open("picocrypt_samples/v1.48/base1000.pcv")
- if err != nil {
- t.Fatal("opening file:", err)
- }
- defer reader.Close()
- _, err = Decrypt("wrong-password", []io.Reader{}, reader, bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrIncorrectPassword) {
- t.Fatal("expected wrong password, got", err)
- }
-}
-
-func TestIncorrectKeyfiles(t *testing.T) {
- argonKey = argon2IDKey
- reader, err := os.Open("picocrypt_samples/v1.48/base0_kf12o.pcv")
- if err != nil {
- t.Fatal("opening file:", err)
- }
- wrongKeyfileData := make([]byte, 100)
- rand.Read(wrongKeyfileData)
- defer reader.Close()
- _, err = Decrypt("password", []io.Reader{bytes.NewBuffer(wrongKeyfileData)}, reader, bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrIncorrectOrMisorderedKeyfiles) {
- t.Fatal("expected wrong keyfieles, got", err)
- }
-}
-
-func TestKeyfilesRequired(t *testing.T) {
- argonKey = argon2IDKey
- reader, err := os.Open("picocrypt_samples/v1.48/base0_kf12o.pcv")
- if err != nil {
- t.Fatal("opening file:", err)
- }
- defer reader.Close()
- _, err = Decrypt("password", []io.Reader{}, reader, bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrKeyfilesRequired) {
- t.Fatal("expected required keyfiles, got", err)
- }
-}
-
-func TestDuplicateKeyfiles(t *testing.T) {
- argonKey = argon2IDKey
- keyfileData := make([]byte, 100)
- rand.Read(keyfileData)
- keyfiles := []io.Reader{}
- for range 2 {
- buff := make([]byte, len(keyfileData))
- copy(buff, keyfileData)
- keyfiles = append(keyfiles, bytes.NewBuffer(buff))
- }
- _, err := EncryptHeadless(
- bytes.NewBuffer([]byte{}),
- "test-password",
- keyfiles,
- Settings{},
- bytes.NewBuffer([]byte{}),
- )
- if !errors.Is(err, ErrDuplicateKeyfiles) {
- t.Fatal("expected ErrDuplicateKeyfiles, got", err)
- }
-}
-
-func TestKeyfilesNotRequired(t *testing.T) {
- argonKey = argon2IDKey
- reader, err := os.Open("picocrypt_samples/v1.48/base1000.pcv")
- if err != nil {
- t.Fatal("opening file:", err)
- }
- defer reader.Close()
- _, err = Decrypt("password", []io.Reader{bytes.NewBuffer([]byte{})}, reader, bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrKeyfilesNotRequired) {
- t.Fatal("expected ErrKeyfilesNotRequired, got", err)
- }
-}
-
-func TestCorrupted(t *testing.T) {
- argonKey = argon2IDKey
- reader, err := os.Open("picocrypt_samples/v1.48/base1000.pcv")
- if err != nil {
- t.Fatal("opening file:", err)
- }
- defer reader.Close()
- r := bytes.NewBuffer([]byte{})
- _, err = io.Copy(r, reader)
- if err != nil {
- t.Fatal("reading file:", err)
- }
- rawBytes := r.Bytes()
- copy(rawBytes[:], []byte("corrupted"))
- corruptedReader := bytes.NewBuffer(rawBytes)
- _, err = Decrypt("qwerty", []io.Reader{}, corruptedReader, bytes.NewBuffer([]byte{}), false)
- if !errors.Is(err, ErrHeaderCorrupted) {
- t.Fatal("expected ErrHeaderCorrupted, got", err)
- }
-}
-
-func TestDamagedButRecoverable(t *testing.T) {
- argonKey = argon2IDKey
- reader, err := os.Open("picocrypt_samples/v1.48/base1000_r.pcv")
- if err != nil {
- t.Fatal("opening file:", err)
- }
- defer reader.Close()
-
- encryptedData, err := io.ReadAll(reader)
- if err != nil {
- t.Fatal("reading file:", err)
- }
-
- encryptedData[1000] = byte(int(encryptedData[1000]) + 1)
-
- damaged, err := Decrypt("password", []io.Reader{}, bytes.NewBuffer(encryptedData), bytes.NewBuffer([]byte{}), false)
- if err != nil {
- t.Fatal("expected no error, got", err)
- }
- if !damaged {
- t.Fatal("expected damage")
- }
-}
-
-func TestCommentsTooLong(t *testing.T) {
- argonKey = argon2IDKey
- comments := make([]byte, maxCommentsLength+1)
- _, err := EncryptHeadless(
- bytes.NewBuffer([]byte{}),
- "test-password",
- []io.Reader{},
- Settings{Comments: string(comments)},
- bytes.NewBuffer([]byte{}),
- )
- if !errors.Is(err, ErrCommentsTooLong) {
- t.Fatal("expected ErrCommentsTooLong, got", err)
- }
-}
diff --git a/internal/encryption/header.go b/internal/encryption/header.go
deleted file mode 100644
index 2c836fb..0000000
--- a/internal/encryption/header.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/binary"
- "fmt"
- "io"
-)
-
-const (
- baseHeaderSize = 789
- versionSize = 5
- commentSize = 5
- flagsSize = 5
-)
-
-type seeds struct {
- // export to allow binary package to fill
- Salt [16]byte
- Nonce [24]byte
- SerpentIV [16]byte
- HkdfSalt [32]byte
- DenySalt [16]byte
- DenyNonce [24]byte
-}
-
-type refs struct {
- keyRef [64]byte
- keyfileRef [32]byte
- macTag [64]byte
-}
-
-type header struct {
- settings Settings
- seeds seeds
- refs refs
- usesKf bool
- nearMiBFlag bool // set when the original data is within 1 chunk of a MiB
-}
-
-func (header *header) bytes(password string) ([]byte, error) {
- data := [][]byte{[]byte(picocryptVersion)}
- data = append(data, []byte(fmt.Sprintf("%05d", len(header.settings.Comments))))
- for _, c := range []byte(header.settings.Comments) {
- data = append(data, []byte{c})
- }
- flags := []bool{
- header.settings.Paranoid,
- header.usesKf,
- header.settings.OrderedKf,
- header.settings.ReedSolomon,
- header.nearMiBFlag,
- }
- flagBytes := make([]byte, len(flags))
- for i, f := range flags {
- if f {
- flagBytes[i] = 1
- }
- }
- data = append(data, flagBytes)
- data = append(data, header.seeds.Salt[:])
- data = append(data, header.seeds.HkdfSalt[:])
- data = append(data, header.seeds.SerpentIV[:])
- data = append(data, header.seeds.Nonce[:])
- data = append(data, header.refs.keyRef[:])
- data = append(data, header.refs.keyfileRef[:])
- data = append(data, header.refs.macTag[:])
-
- headerBytes := make([]byte, baseHeaderSize+3*len(header.settings.Comments))
- written := 0
- for _, d := range data {
- err := rsEncode(headerBytes[written:written+len(d)*3], d)
- if err != nil {
- return nil, err
- }
- written += len(d) * 3
- }
-
- if header.settings.Deniability {
- denyStream := newDeniabilityStream(password, header)
- var err error
- headerBytes, err = denyStream.stream(headerBytes)
- if err != nil {
- return nil, fmt.Errorf("denying header data: %w", err)
- }
- headerBytes = append(append(header.seeds.DenySalt[:], header.seeds.DenyNonce[:]...), headerBytes...)
- }
-
- return headerBytes, nil
-}
-
-func randomSeeds() (seeds, error) {
- raw := make([]byte, binary.Size(seeds{}))
- _, err := io.ReadFull(rand.Reader, raw)
- if err != nil {
- return seeds{}, err
- }
- decoded := seeds{}
- err = binary.Read(bytes.NewBuffer(raw), binary.BigEndian, &decoded)
- return decoded, err
-}
diff --git a/internal/encryption/keys.go b/internal/encryption/keys.go
deleted file mode 100644
index ec1e590..0000000
--- a/internal/encryption/keys.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "errors"
- "fmt"
- "hash"
- "io"
-
- "golang.org/x/crypto/argon2"
- "golang.org/x/crypto/hkdf"
- "golang.org/x/crypto/sha3"
-)
-
-type keys struct {
- key [32]byte
- macKey [32]byte
- serpentKey [32]byte
- denyKey [32]byte
- hkdf io.Reader
- keyRef [64]byte
- keyfileRef [32]byte
-}
-
-func xor(a, b [32]byte) [32]byte {
- var result [32]byte
- for i := range a {
- result[i] = a[i] ^ b[i]
- }
- return result
-}
-
-func generateKeyfileKey(ordered bool, keyfiles []io.Reader) ([32]byte, error) {
- if len(keyfiles) == 0 {
- return [32]byte{}, nil
- }
-
- hashes := make([][32]byte, len(keyfiles))
- hasher := sha3.New256()
- for i, file := range keyfiles {
- if err := computeHash(hasher, file, hashes[i][:]); err != nil {
- return [32]byte{}, err
- }
- if !ordered {
- hasher.Reset()
- }
- }
-
- if ordered {
- key := [32]byte{}
- copy(key[:], hasher.Sum(nil))
- return key, nil
- }
-
- key := [32]byte{}
- for _, hash := range hashes {
- key = xor(key, hash)
- }
- if hasDuplicates(hashes) {
- return key, ErrDuplicateKeyfiles
- }
- return key, nil
-}
-
-func hasDuplicates(hashes [][32]byte) bool {
- hashSet := make(map[string]struct{}, len(hashes))
- for _, hash := range hashes {
- hashStr := string(hash[:])
- if _, exists := hashSet[hashStr]; exists {
- return true
- }
- hashSet[hashStr] = struct{}{}
- }
- return false
-}
-
-func argon2IDKey(password string, salt [16]byte, iterations uint32, parallelism uint8) [32]byte {
- var key [32]byte
- copy(key[:], argon2.IDKey([]byte(password), salt[:], iterations, 1<<20, parallelism, 32))
- return key
-}
-
-var argonKey = argon2IDKey
-
-func generatePasswordKey(password string, salt [16]byte, paranoid bool) [32]byte {
- iterations := uint32(4)
- parallelism := uint8(4)
- if paranoid {
- iterations = 8
- parallelism = 8
- }
- return argonKey(password, salt, iterations, parallelism)
-}
-
-func generateDenyKey(password string, salt [16]byte) [32]byte {
- return argonKey(password, salt, 4, 4)
-}
-
-func newKeys(settings Settings, seeds seeds, password string, keyfiles []io.Reader) (keys, error) {
- keyfileKey, err := generateKeyfileKey(settings.OrderedKf, keyfiles)
- if err != nil && !errors.Is(err, ErrDuplicateKeyfiles) {
- return keys{}, fmt.Errorf("creating keys: %w", err)
- }
- duplicateKeyfiles := errors.Is(err, ErrDuplicateKeyfiles)
-
- passwordKey := generatePasswordKey(password, seeds.Salt, settings.Paranoid)
-
- var keyRef [64]byte
- err = computeHash(sha3.New512(), bytes.NewBuffer(passwordKey[:]), keyRef[:])
- if err != nil {
- return keys{}, fmt.Errorf("creating keys: %w", err)
- }
- var keyfileRef [32]byte
- if len(keyfiles) > 0 {
- computeHash(sha3.New256(), bytes.NewBuffer(keyfileKey[:]), keyfileRef[:])
- }
-
- key := xor(keyfileKey, passwordKey)
-
- var denyKey [32]byte
- if settings.Deniability {
- denyKey = generateDenyKey(password, seeds.DenySalt)
- }
-
- hkdf := hkdf.New(sha3.New256, key[:], seeds.HkdfSalt[:], nil)
- var macKey [32]byte
- if _, err := io.ReadFull(hkdf, macKey[:]); err != nil {
- return keys{}, fmt.Errorf("filling macKey: %w", err)
- }
- var serpentKey [32]byte
- if _, err := io.ReadFull(hkdf, serpentKey[:]); err != nil {
- return keys{}, fmt.Errorf("filling serpentKey: %w", err)
- }
-
- keys := keys{
- key: key,
- macKey: macKey,
- serpentKey: serpentKey,
- denyKey: denyKey,
- hkdf: hkdf,
- keyRef: keyRef,
- keyfileRef: keyfileRef,
- }
-
- if duplicateKeyfiles {
- return keys, ErrDuplicateKeyfiles
- }
- return keys, nil
-}
-
-func computeHash(hasher hash.Hash, src io.Reader, dest []byte) error {
- data, err := io.ReadAll(src)
- if err != nil {
- return fmt.Errorf("reading src: %w", err)
- }
- _, err = hasher.Write(data)
- if err != nil {
- return fmt.Errorf("hashing src: %w", err)
- }
- copy(dest, hasher.Sum(nil))
- return nil
-}
diff --git a/internal/encryption/keys_test.go b/internal/encryption/keys_test.go
deleted file mode 100644
index 288012d..0000000
--- a/internal/encryption/keys_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "crypto/rand"
- "errors"
- "io"
- "testing"
-)
-
-func TestXor(t *testing.T) {
- zeros := [32]byte{}
- ones := [32]byte{}
- for i := 0; i < len(ones); i++ {
- ones[i] = 255
- }
-
- result := xor(zeros, zeros)
- if !bytes.Equal(zeros[:], result[:]) {
- t.Fatal("xor zeros with zeros should be zero")
- }
-
- result = xor(ones, ones)
- if !bytes.Equal(zeros[:], result[:]) {
- t.Fatal("xor ones with ones should be zero")
- }
-
- result = xor(zeros, ones)
- if !bytes.Equal(ones[:], result[:]) {
- t.Fatal("xor zeros with ones should be ones")
- }
-
- result = xor(ones, zeros)
- if !bytes.Equal(ones[:], result[:]) {
- t.Fatal("xor ones with zeros should be zero")
- }
-}
-
-func TestGenKeyfileKeyOrdered(t *testing.T) {
- kf1 := make([]byte, 100)
- rand.Read(kf1)
- kf2 := make([]byte, 200)
- rand.Read(kf2)
-
- kf12 := []io.Reader{bytes.NewBuffer(kf1), bytes.NewBuffer(kf2)}
- key12, err := generateKeyfileKey(true, kf12)
- if err != nil {
- t.Fatal("generating keyfile key:", err)
- }
-
- kf21 := []io.Reader{bytes.NewBuffer(kf2), bytes.NewBuffer(kf1)}
- key21, err := generateKeyfileKey(true, kf21)
- if err != nil {
- t.Fatal("generating keyfile key:", err)
- }
-
- if bytes.Equal(key12[:], key21[:]) {
- t.Fatal("key order should change result")
- }
-}
-
-func TestGenKeyfileKeyUnordered(t *testing.T) {
- kf1 := make([]byte, 100)
- rand.Read(kf1)
- kf2 := make([]byte, 200)
- rand.Read(kf2)
-
- kf12 := []io.Reader{bytes.NewBuffer(kf1), bytes.NewBuffer(kf2)}
- key12, err := generateKeyfileKey(false, kf12)
- if err != nil {
- t.Fatal("generating keyfile key:", err)
- }
-
- kf21 := []io.Reader{bytes.NewBuffer(kf2), bytes.NewBuffer(kf1)}
- key21, err := generateKeyfileKey(false, kf21)
- if err != nil {
- t.Fatal("generating keyfile key:", err)
- }
-
- if !bytes.Equal(key12[:], key21[:]) {
- t.Fatal("key order should not change result")
- }
-}
-
-func TestGenKeyfileKeyDuplicated(t *testing.T) {
- kf1 := make([]byte, 100)
- kf2 := make([]byte, 100)
- kf3 := make([]byte, 100)
- rand.Read(kf1)
- copy(kf2[:], kf1[:])
- rand.Read(kf3)
- key, err := generateKeyfileKey(
- false,
- []io.Reader{bytes.NewBuffer(kf1), bytes.NewBuffer(kf2), bytes.NewBuffer(kf3)},
- )
- if !errors.Is(err, ErrDuplicateKeyfiles) {
- t.Fatal("expected duplicate keyfile error")
- }
- if bytes.Equal(key[:], make([]byte, 32)) {
- t.Fatal("key should still be generated")
- }
-}
diff --git a/internal/encryption/picocrypt_compatibility_test.go b/internal/encryption/picocrypt_compatibility_test.go
deleted file mode 100644
index 470af6d..0000000
--- a/internal/encryption/picocrypt_compatibility_test.go
+++ /dev/null
@@ -1,357 +0,0 @@
-// Test for combatibility with Picocrypt
-package encryption
-
-import (
- "bytes"
- "crypto/sha256"
- "encoding/hex"
- "hash"
- "io"
- "log"
- "os"
- "strconv"
- "strings"
- "testing"
-)
-
-var compatibilityVersions = []string{"v1.47", "v1.48"}
-
-/* Decrypt files encrypted by Picocrypt
-
-To generate the test files, choose a version of Picocrypt and rotate through each file under
-picocrypt_samples/basefiles. The file sizes tested are:
-- 0 bytes. While encrypted empyt files is not really meaningful, encryption should still work
-- 1000 bytes. Just a generic small file of data
-- 1048570 bytes. This should trigger the extra flag for Reed-Solomon encoding that Picocrypt uses
-
-Encrypt each file with password "password". Append the following strings to the filename to indicate
-what other encryption settings were used. Note that only the keyfile settings are actually used in
-the test because the others can be derived from the header data. The non-keyfile suffixes are just
-for reference to check what combinations of settings are being tested.
-- "_c" indicates that comments were used
-- "_p" indicates that paranoid mode was used
-- "_r" indicates that Reed-Solomon encoding was used
-- "_d" indicates that deniability mode was used
-- "_kf1" indicates that keyfile 1 was used
-- "_kf12" indicates that keyfiles 1 and 2 were used
-- "_kf12o" indicates that keyfiles 1 and 2 were used, and the keyfiles were ordered
-
-The encrypted files should be stored under picocrypt_samples//
-*/
-
-func getTestKeyfiles(name string) []io.Reader {
- kf := []io.Reader{}
- usesKf1 := strings.Contains(name, "kf1")
- usesKf2 := strings.Contains(name, "kf12")
- for i, used := range []bool{usesKf1, usesKf2} {
- if !used {
- continue
- }
- r, err := os.Open("picocrypt_samples/keyfiles/keyfile" + strconv.Itoa(i+1))
- if err != nil {
- log.Fatal("opening keyfile: %w", err)
- }
- defer r.Close()
- buf := bytes.NewBuffer([]byte{})
- _, err = io.Copy(buf, r)
- if err != nil {
- log.Fatal("reading keyfile: %w", err)
- }
- kf = append(kf, buf)
- }
- return kf
-}
-
-func getBaseData(name string) []byte {
- basename := strings.SplitN(name, ".", 2)[0]
- if strings.Contains(basename, "_") {
- basename = strings.SplitN(basename, "_", 2)[0]
- }
- r, err := os.Open("picocrypt_samples/basefiles/" + basename)
- if err != nil {
- log.Fatal("opening file: %w", err)
- }
- defer r.Close()
- buf := bytes.NewBuffer([]byte{})
- _, err = io.Copy(buf, r)
- if err != nil {
- log.Fatal("reading file: %w", err)
- }
- return buf.Bytes()
-}
-
-func TestDecryptingPicocryptFiles(t *testing.T) {
- for _, version := range compatibilityVersions {
- files, err := os.ReadDir("picocrypt_samples/" + version)
- if err != nil {
- t.Fatal("reading directory: %w", err)
- }
- for _, file := range files {
- if !(strings.HasSuffix(file.Name(), ".pcv")) {
- continue
- }
- t.Run(version+":"+file.Name(), func(t *testing.T) {
- r, err := os.Open("picocrypt_samples/" + version + "/" + file.Name())
- if err != nil {
- t.Fatal("opening encrypted file: %w", err)
- }
- defer r.Close()
- w := bytes.NewBuffer([]byte{})
- kf := getTestKeyfiles(file.Name())
- damaged, err := Decrypt("password", kf, r, w, false)
- if damaged {
- t.Fatal("damaged data")
- }
- if err != nil {
- t.Fatal("decrypting:", err)
- }
- result := w.Bytes()
- expected := getBaseData(file.Name())
- if !bytes.Equal(result, expected) {
- t.Fatal("decrypted data does not match")
- }
- })
- }
- }
-}
-
-/* Test encryption parity with Picocrypt
-
-Given the same header (seeds, settings, etc), same password, and same keyfiles, the encryption
-package should produce exactly the same output as Picocrypt. Run through each file in the
-latest version and ensure that the encrypted data matches exactly.
-*/
-
-func extractSettings(path string) (Settings, seeds) {
- r, err := os.Open(path)
- if err != nil {
- log.Fatal("opening file: %w", err)
- }
- defer r.Close()
- header, err := getHeader(r, "password")
- if err != nil {
- log.Fatal("reading header: %w", err)
- }
- return header.settings, header.seeds
-}
-
-func TestEncryptedFilesMatchPicocrypt(t *testing.T) {
- latestVersion := compatibilityVersions[len(compatibilityVersions)-1]
- files, err := os.ReadDir("picocrypt_samples/" + latestVersion)
- if err != nil {
- t.Fatal("reading directory: %w", err)
- }
- for _, file := range files {
- if !(strings.HasSuffix(file.Name(), ".pcv")) {
- continue
- }
- t.Run("encrypting:"+file.Name(), func(t *testing.T) {
- path := "picocrypt_samples/" + latestVersion + "/" + file.Name()
- settings, seeds := extractSettings(path)
- kf := getTestKeyfiles(file.Name())
- w := bytes.NewBuffer([]byte{})
- header, err := encryptWithSeeds(
- bytes.NewBuffer(getBaseData(file.Name())),
- "password",
- kf,
- settings,
- w,
- seeds,
- )
- if err != nil {
- t.Fatal("encrypting:", err)
- }
- full := bytes.NewBuffer([]byte{})
- err = PrependHeader(bytes.NewBuffer(w.Bytes()), full, header)
- if err != nil {
- t.Fatal("prepending header:", err)
- }
- result := full.Bytes()
-
- r, err := os.Open(path)
- if err != nil {
- t.Fatal("opening encrypted file: %w", err)
- }
- defer r.Close()
- buf := bytes.NewBuffer([]byte{})
- _, err = io.Copy(buf, r)
- if err != nil {
- t.Fatal("reading file: %w", err)
- }
- expected := buf.Bytes()
-
- if !bytes.Equal(result, expected) {
- t.Fatal("encrypted data does not match")
- }
- })
- }
-}
-
-/* Compare encryption and decryption for very large files
-
-The encryption and decryption methods rotate the nonces every 60 GB. To accurately test this,
-encrypt/decrypt files larger than 60 GB. Instead of adding files that large to the repository,
-create a test that simulates this behavior. These tests will generate files of zeros, encrypt
-them, and then decrypt them. The sha256 of the encrypted and decrypted files are compared against
-expected values generated by hand, following these steps:
-
-1. Create a file of zeros (bash ex: dd if=/dev/zero of=zerofile bs=1M count=62464)
-2. Get the sha256 of the file (bash ex: sha256sum zerofile)
-3. Encrypt the file using the latest Picocrypt version. This is a long test, so only check against
- one version
-4. Get the sha256sum of the encrypted file (bash ex: sha256sum zerofile.pcv)
-5. Save the header bytes from the encrypted file (bash ex: head -c 789 zerofile.pcv > zerofile.header).
-6. Create a test with the header bytes and the sha256sums
-*/
-
-type zeroReader struct {
- size int64
- counter int64
-}
-
-func (z *zeroReader) Read(p []byte) (n int, err error) {
- if z.counter == z.size {
- return 0, io.EOF
- }
- for i := range p {
- p[i] = 0
- z.counter++
- if z.counter == z.size {
- return i + 1, nil
- }
- }
- return len(p), nil
-}
-
-type shaDecryptWriter struct {
- decryptStream *decryptStream
- encryptedSha hash.Hash
- decryptedSha hash.Hash
-}
-
-func (s *shaDecryptWriter) Write(p []byte) (int, error) {
- _, err := s.encryptedSha.Write(p)
- if err != nil {
- return 0, err
- }
- decoded, err := s.decryptStream.stream(p)
- if err != nil {
- return 0, err
- }
- _, err = s.decryptedSha.Write(decoded)
- if err != nil {
- return 0, err
- }
- return len(p), nil
-}
-
-func (s *shaDecryptWriter) shas() ([]byte, []byte, error) {
- decoded, err := s.decryptStream.flush()
- if err != nil {
- return nil, nil, err
- }
- _, err = s.decryptedSha.Write(decoded)
- if err != nil {
- return nil, nil, err
- }
- return s.encryptedSha.Sum(nil), s.decryptedSha.Sum(nil), nil
-}
-
-func compareShas(
- t *testing.T,
- password string,
- headerFilename string,
- encodedSha string,
- decodedSha string,
- zeroFileSize int64,
-) {
- headerReader, err := os.Open(headerFilename)
- if err != nil {
- t.Fatal("opening header file:", err)
- }
- defer headerReader.Close()
- headerBytes, err := io.ReadAll(headerReader)
- if err != nil {
- t.Fatal("reading header:", err)
- }
-
- damageTracker := &damageTracker{}
- writer := &shaDecryptWriter{makeDecryptStream(password, nil, damageTracker), sha256.New(), sha256.New()}
- _, err = writer.Write(headerBytes)
- if err != nil {
- t.Fatal("writing header:", err)
- }
- if !writer.decryptStream.headerStream.isDone() {
- t.Fatal("header stream should be done")
- }
-
- _, err = encryptWithSeeds(
- &zeroReader{size: zeroFileSize},
- password,
- []io.Reader{},
- writer.decryptStream.headerStream.header.settings,
- writer,
- writer.decryptStream.headerStream.header.seeds,
- )
- if err != nil {
- t.Fatal("encrypting:", err)
- }
-
- eSha, dSha, err := writer.shas()
- if err != nil {
- t.Fatal("getting shas:", err)
- }
- if hex.EncodeToString(eSha) != encodedSha {
- t.Fatal("encoded sha256 does not match")
- }
- if hex.EncodeToString(dSha) != decodedSha {
- t.Fatal("decoded sha256 does not match")
- }
-}
-
-func isLatestVersion(path string) bool {
- r, err := os.Open(path)
- if err != nil {
- log.Fatal("opening file: %w", err)
- }
- defer r.Close()
- expected := compatibilityVersions[len(compatibilityVersions)-1]
- buf := make([]byte, len(expected))
- _, err = io.ReadFull(r, buf)
- if err != nil {
- log.Fatal("reading file: %w", err)
- }
- return string(buf) == expected
-}
-
-func TestSmallFileSha(t *testing.T) {
- // Test 1K file of zeros, for debugging
- path := "picocrypt_samples/headers/zerofile1024.header"
- if !isLatestVersion(path) {
- t.Fatal("header file is not latest version")
- }
- compareShas(
- t,
- "password",
- path,
- "bb8a1f6be9cfcd39cb8eedd49583ddfd7153158b8ffce541e256deb400160a98",
- "5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
- (1 << 10), // 1K
- )
-}
-
-func TestLargeFileSha(t *testing.T) {
- // Test 65GB file of zeros
- path := "picocrypt_samples/headers/zerofile65498251264.header"
- if !isLatestVersion(path) {
- t.Fatal("header file is not latest version")
- }
- compareShas(
- t,
- "password",
- path,
- "451dd75500b9502e7c82fa679e698443337d33c52ddb84d5a14c3ff95032dc50",
- "f3f0d678fa138e4581ed15ec63f8cb965e5d7b722db7d5fc4877e763163d399c",
- 65498251264,
- )
-}
diff --git a/internal/encryption/picocrypt_samples/basefiles/base0 b/internal/encryption/picocrypt_samples/basefiles/base0
deleted file mode 100644
index e69de29..0000000
diff --git a/internal/encryption/picocrypt_samples/basefiles/base1000 b/internal/encryption/picocrypt_samples/basefiles/base1000
deleted file mode 100644
index 23bf4d1..0000000
Binary files a/internal/encryption/picocrypt_samples/basefiles/base1000 and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/basefiles/base1048570 b/internal/encryption/picocrypt_samples/basefiles/base1048570
deleted file mode 100644
index fcdcf72..0000000
Binary files a/internal/encryption/picocrypt_samples/basefiles/base1048570 and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/headers/zerofile1024.header b/internal/encryption/picocrypt_samples/headers/zerofile1024.header
deleted file mode 100644
index 1cb9f4a..0000000
Binary files a/internal/encryption/picocrypt_samples/headers/zerofile1024.header and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/headers/zerofile65498251264.header b/internal/encryption/picocrypt_samples/headers/zerofile65498251264.header
deleted file mode 100644
index 46685eb..0000000
Binary files a/internal/encryption/picocrypt_samples/headers/zerofile65498251264.header and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/keyfiles/keyfile1 b/internal/encryption/picocrypt_samples/keyfiles/keyfile1
deleted file mode 100644
index 0ded3bc..0000000
--- a/internal/encryption/picocrypt_samples/keyfiles/keyfile1
+++ /dev/null
@@ -1,2 +0,0 @@
-y»_œòÒi<2üL¢ëîtÀ¥
-©/¹gÎàíUü}Ã
\ No newline at end of file
diff --git a/internal/encryption/picocrypt_samples/keyfiles/keyfile2 b/internal/encryption/picocrypt_samples/keyfiles/keyfile2
deleted file mode 100644
index 0e00624..0000000
--- a/internal/encryption/picocrypt_samples/keyfiles/keyfile2
+++ /dev/null
@@ -1 +0,0 @@
-—¡ÀÛ¦‘°a÷vyò“RÄ@Hÿ½ðR©tø»jØ
\ No newline at end of file
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0.pcv b/internal/encryption/picocrypt_samples/v1.47/base0.pcv
deleted file mode 100644
index 222634f..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_c.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_c.pcv
deleted file mode 100644
index 8974a4b..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_c.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_d.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_d.pcv
deleted file mode 100644
index 5b1db93..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_d.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_kf1.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_kf1.pcv
deleted file mode 100644
index 8b10b86..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_kf1.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_kf12.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_kf12.pcv
deleted file mode 100644
index ee7132e..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_kf12.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_kf12o.pcv
deleted file mode 100644
index ec18ff5..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_p.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_p.pcv
deleted file mode 100644
index 9285d76..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_p.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_p_r_d_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_p_r_d_kf12o.pcv
deleted file mode 100644
index 78c5c42..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_p_r_d_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base0_r.pcv b/internal/encryption/picocrypt_samples/v1.47/base0_r.pcv
deleted file mode 100644
index fab7831..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base0_r.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000.pcv
deleted file mode 100644
index 40ba8bc..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_c.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_c.pcv
deleted file mode 100644
index 7d5b20c..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_c.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_c_p_r_d_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_c_p_r_d_kf12o.pcv
deleted file mode 100644
index 3eb13dd..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_c_p_r_d_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_d.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_d.pcv
deleted file mode 100644
index e3fb60a..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_d.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_kf1.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_kf1.pcv
deleted file mode 100644
index dc876cc..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_kf1.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_kf12.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_kf12.pcv
deleted file mode 100644
index 99e3bdd..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_kf12.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_kf12o.pcv
deleted file mode 100644
index ba356d4..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_p.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_p.pcv
deleted file mode 100644
index f28ee95..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_p.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1000_r.pcv b/internal/encryption/picocrypt_samples/v1.47/base1000_r.pcv
deleted file mode 100644
index 828b2e0..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1000_r.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570.pcv
deleted file mode 100644
index 0568791..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_c.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_c.pcv
deleted file mode 100644
index 35adce8..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_c.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_d.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_d.pcv
deleted file mode 100644
index d3d963c..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_d.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_kf1.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_kf1.pcv
deleted file mode 100644
index 5f5f13c..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_kf1.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_kf12.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_kf12.pcv
deleted file mode 100644
index dce9dae..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_kf12.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_kf12o.pcv
deleted file mode 100644
index 33b235d..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_p.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_p.pcv
deleted file mode 100644
index b6cca44..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_p.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_p_r_d_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_p_r_d_kf12o.pcv
deleted file mode 100644
index e5027f2..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_p_r_d_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.47/base1048570_r.pcv b/internal/encryption/picocrypt_samples/v1.47/base1048570_r.pcv
deleted file mode 100644
index 731967f..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.47/base1048570_r.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0.pcv b/internal/encryption/picocrypt_samples/v1.48/base0.pcv
deleted file mode 100644
index 070fb5f..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_c.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_c.pcv
deleted file mode 100644
index 06f8368..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_c.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_d.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_d.pcv
deleted file mode 100644
index f69a187..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_d.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_kf1.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_kf1.pcv
deleted file mode 100644
index 3cb1c32..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_kf1.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_kf12.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_kf12.pcv
deleted file mode 100644
index b3effa7..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_kf12.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_kf12o.pcv
deleted file mode 100644
index ee45ed8..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_p.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_p.pcv
deleted file mode 100644
index 96eea27..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_p.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_p_r_d_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_p_r_d_kf12o.pcv
deleted file mode 100644
index 9a1e4c8..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_p_r_d_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base0_r.pcv b/internal/encryption/picocrypt_samples/v1.48/base0_r.pcv
deleted file mode 100644
index 7436238..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base0_r.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000.pcv
deleted file mode 100644
index ed98ee9..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_c.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_c.pcv
deleted file mode 100644
index e6e666a..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_c.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_d.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_d.pcv
deleted file mode 100644
index 73e0176..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_d.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_kf1.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_kf1.pcv
deleted file mode 100644
index 4fc1f41..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_kf1.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_kf12.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_kf12.pcv
deleted file mode 100644
index 954780b..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_kf12.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_kf12o.pcv
deleted file mode 100644
index 875be8f..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_p.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_p.pcv
deleted file mode 100644
index 266017f..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_p.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_p_r_d_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_p_r_d_kf12o.pcv
deleted file mode 100644
index e887495..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_p_r_d_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1000_r.pcv b/internal/encryption/picocrypt_samples/v1.48/base1000_r.pcv
deleted file mode 100644
index 0464fb7..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1000_r.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570.pcv
deleted file mode 100644
index 4be8049..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_c.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_c.pcv
deleted file mode 100644
index ef333f8..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_c.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_d.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_d.pcv
deleted file mode 100644
index f17fe97..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_d.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_kf1.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_kf1.pcv
deleted file mode 100644
index c588cc3..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_kf1.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_kf12.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_kf12.pcv
deleted file mode 100644
index 8b97e97..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_kf12.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_kf12o.pcv
deleted file mode 100644
index a8f74cb..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_p.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_p.pcv
deleted file mode 100644
index 5cb0674..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_p.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_p_r_d_kf12o.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_p_r_d_kf12o.pcv
deleted file mode 100644
index 704613a..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_p_r_d_kf12o.pcv and /dev/null differ
diff --git a/internal/encryption/picocrypt_samples/v1.48/base1048570_r.pcv b/internal/encryption/picocrypt_samples/v1.48/base1048570_r.pcv
deleted file mode 100644
index 14206a3..0000000
Binary files a/internal/encryption/picocrypt_samples/v1.48/base1048570_r.pcv and /dev/null differ
diff --git a/internal/encryption/reed_solomon.go b/internal/encryption/reed_solomon.go
deleted file mode 100644
index b640bc0..0000000
--- a/internal/encryption/reed_solomon.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package encryption
-
-import (
- "bytes"
- "fmt"
- "sync"
-
- "github.com/Picocrypt/infectious"
-)
-
-const (
- chunkSize = 128
- encodedSize = 136
-)
-
-var (
- fecs = make(map[[2]int]*infectious.FEC)
- fecsMu sync.Mutex
-)
-
-func getFEC(encoded, decoded []byte) (*infectious.FEC, error) {
- size := [2]int{len(decoded), len(encoded)}
-
- fecsMu.Lock()
- defer fecsMu.Unlock()
-
- fec := fecs[size]
- if fec != nil {
- return fec, nil
- }
-
- fec, err := infectious.NewFEC(size[0], size[1])
- if err != nil {
- return fec, err
- }
-
- fecs[size] = fec
- return fec, nil
-}
-
-func rsEncode(dst, src []byte) error {
- fec, err := getFEC(dst, src)
- if err != nil {
- return fmt.Errorf("getting FEC: %w", err)
- }
- return fec.Encode(src, func(s infectious.Share) { dst[s.Number] = s.Data[0] })
-}
-
-func rsDecode(dst, src []byte, skip bool) (bool, bool, error) {
- if skip {
- copy(dst, src[:len(dst)])
- return false, false, nil
- }
-
- // Encoding is much faster than decoding. Try re-encoding the original
- // bytes and if the result matches, there must have been no corruption.
- recoded := make([]byte, len(src))
- rsEncode(recoded, src[:len(dst)])
- if bytes.Equal(recoded, src) {
- copy(dst, src[:len(dst)])
- return false, false, nil
- }
-
- // Attempt to recover damaged data
- fec, err := getFEC(src, dst)
- if err != nil {
- return true, false, fmt.Errorf("getting FEC: %w", err)
- }
- tmp := make([]infectious.Share, fec.Total())
- for i := 0; i < fec.Total(); i++ {
- tmp[i].Number = i
- tmp[i].Data = []byte{src[i]}
- }
- res, err := fec.Decode(nil, tmp)
- if err == nil {
- copy(dst, res)
- return true, false, nil
- }
-
- // Fully corrupted - use a best guess
- copy(dst, src[:len(dst)])
- return true, true, nil
-}
-
-type rsEncodeStream struct {
- buff []byte
- chunksEncoded int64
- header *header
-}
-
-func (r *rsEncodeStream) stream(p []byte) ([]byte, error) {
- r.buff = append(r.buff, p...)
- nChunks := len(r.buff) / chunkSize
- rsData := make([]byte, nChunks*encodedSize)
- for i := 0; i < nChunks; i++ {
- err := rsEncode(rsData[i*encodedSize:(i+1)*encodedSize], r.buff[i*chunkSize:(i+1)*chunkSize])
- if err != nil {
- return nil, err
- }
- }
- r.buff = r.buff[nChunks*chunkSize:]
- r.chunksEncoded += int64(nChunks)
- return rsData, nil
-}
-
-func (r *rsEncodeStream) flush() ([]byte, error) {
- // Skip if exactly the right number of chunks encoded and there is no buffer data
- exactMiB := r.chunksEncoded%((1<<20)/chunkSize) == 0
- if exactMiB && len(r.buff) == 0 {
- return nil, nil
- }
- padding := make([]byte, chunkSize-len(r.buff))
- for i := range padding {
- padding[i] = byte(chunkSize - len(r.buff))
- }
- dst := make([]byte, encodedSize)
- err := rsEncode(dst, append(r.buff, padding...))
- if err != nil {
- return nil, fmt.Errorf("encoding final chunk: %w", err)
- }
- return dst, nil
-}
-
-type rsDecodeStream struct {
- buff []byte
- skip bool
- damageTracker *damageTracker
- header *header
- chunksDecoded int64
-}
-
-func (r *rsDecodeStream) stream(p []byte) ([]byte, error) {
- r.buff = append(r.buff, p...)
- nChunks := len(r.buff) / encodedSize
- // The last chunk might be padded, so keep it in the buffer for flush
- if ((len(r.buff) % encodedSize) == 0) && (nChunks > 0) {
- nChunks -= 1
- }
- rsData := make([]byte, nChunks*chunkSize)
- for i := 0; i < nChunks; i++ {
- src := r.buff[i*encodedSize : (i+1)*encodedSize]
- dst := rsData[i*chunkSize : (i+1)*chunkSize]
- damaged, _, err := rsDecode(dst, src, r.skip)
- r.damageTracker.damage = r.damageTracker.damage || damaged
- if err != nil {
- return nil, err
- }
- }
- r.chunksDecoded += int64(nChunks)
- r.buff = r.buff[nChunks*encodedSize:]
- return rsData, nil
-}
-
-func (r *rsDecodeStream) flush() ([]byte, error) {
- if len(r.buff) == 0 {
- return nil, nil
- }
- if len(r.buff) != encodedSize {
- return nil, ErrBodyCorrupted
- }
- res := make([]byte, chunkSize)
- damaged, _, err := rsDecode(res, r.buff, r.skip)
- r.damageTracker.damage = r.damageTracker.damage || damaged
- if err != nil {
- return nil, err
- }
- // The last chunk is padded unless there is an exact multiple of MiB of decoded data
- // and the nearMiBFlag is not set
- exactMiB := (r.chunksDecoded+1)%((1<<20)/chunkSize) == 0
- if exactMiB && !r.header.nearMiBFlag {
- return res, nil
- }
- keep := chunkSize - int(res[chunkSize-1])
- if (keep >= 0) && (keep < chunkSize) {
- return res[:keep], err
- }
- return nil, ErrBodyCorrupted
-}
-
-func makeRSEncodeStream(header *header) *rsEncodeStream {
- return &rsEncodeStream{header: header}
-}
-
-func makeRSDecodeStream(skip bool, header *header, damageTracker *damageTracker) *rsDecodeStream {
- return &rsDecodeStream{skip: skip, damageTracker: damageTracker, header: header}
-}
diff --git a/internal/encryption/stream.go b/internal/encryption/stream.go
deleted file mode 100644
index b86b228..0000000
--- a/internal/encryption/stream.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package encryption
-
-type streamer interface {
- stream(p []byte) ([]byte, error)
-}
-
-type streamerFlusher interface {
- streamer
- flush() ([]byte, error)
-}
-
-func streamStack[T streamer](streams []T, p []byte) ([]byte, error) {
- var err error
- for _, stream := range streams {
- p, err = stream.stream(p)
- if err != nil {
- return nil, err
- }
- if len(p) == 0 {
- break
- }
- }
- return p, nil
-}
-
-func flushStack(streams []streamerFlusher) ([]byte, error) {
- p := []byte{}
- for _, stream := range streams {
- pStream, err := stream.stream(p)
- if err != nil {
- return nil, err
- }
- pFlush, err := stream.flush()
- if err != nil {
- return nil, err
- }
- p = append(pStream, pFlush...)
- }
- return p, nil
-}
diff --git a/internal/ui/elements.go b/internal/ui/elements.go
deleted file mode 100644
index e7aac48..0000000
--- a/internal/ui/elements.go
+++ /dev/null
@@ -1,291 +0,0 @@
-package ui
-
-import (
- "crypto/rand"
- "errors"
- "fmt"
- "time"
-
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/container"
- "fyne.io/fyne/v2/dialog"
- "fyne.io/fyne/v2/layout"
- "fyne.io/fyne/v2/theme"
- "fyne.io/fyne/v2/widget"
-)
-
-func MakeInfoBtn(w fyne.Window) *widget.Button { // coverage-ignore
- btn := widget.NewButtonWithIcon("", theme.InfoIcon(), func() {
- title := "PicoGo " + PicoGoVersion
- message := "This app is not sponsored or supported by Picocrypt. It is a 3rd party " +
- "app written to make Picocrypt files more easily accessible on mobile devices.\n\n" +
- "If you have any problems, please report them so that they can be fixed."
- confirm := dialog.NewInformation(title, message, w)
- confirm.Show()
- })
- return btn
-}
-
-func writeLogsCallback(logger *Logger, window fyne.Window) func(fyne.URIWriteCloser, error) {
- return func(writer fyne.URIWriteCloser, err error) {
- if writer != nil {
- defer writer.Close()
- }
- if err != nil { // coverage-ignore
- logger.Log("Writing logs failed", State{}, err)
- dialog.ShowError(fmt.Errorf("writing logs: %w", err), window)
- return
- }
- if writer != nil {
- writer.Write([]byte(logger.CsvString()))
- }
- }
-}
-
-func writeLogs(logger *Logger, window fyne.Window) { // coverage-ignore
- d := dialog.NewFileSave(writeLogsCallback(logger, window), window)
- d.SetFileName("picogo-logs.csv")
- d.Show()
-}
-
-func MakeLogBtn(logger *Logger, w fyne.Window) *widget.Button { // coverage-ignore
- btn := widget.NewButtonWithIcon("", theme.MailSendIcon(), func() {
- title := "Save Logs"
- message := "Save log data to assist with issue reporting. Sensitive data (passwords, file names, etc.) " +
- "will not be recorded, but you should still review the logs before sharing to ensure you are " +
- "comfortable with the data being shared."
- text := widget.NewLabel(message)
- text.Wrapping = fyne.TextWrapWord
- dialog.ShowCustomConfirm(title, "Save Logs", "Dismiss", text, func(b bool) {
- if b {
- writeLogs(logger, w)
- }
- }, w)
- })
- return btn
-}
-
-func MakeSettingsBtn(settings *Settings, parent fyne.Window) *widget.Button { // coverage-ignore
- btn := widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
- dialog.ShowCustom(
- "Default Settings",
- "Close",
- container.New(
- layout.NewVBoxLayout(),
- settings.ReedSolomonDefault,
- settings.ParanoidDefault,
- settings.DeniabilityDefault,
- settings.OrderedKfDefault,
- ),
- parent,
- )
- })
- return btn
-}
-
-func filePickerCallback(state *State, logger *Logger, w fyne.Window) func(fyne.URIReadCloser, error) {
- return func(reader fyne.URIReadCloser, err error) {
- if reader != nil {
- defer reader.Close()
- }
- if err != nil { // coverage-ignore
- logger.Log("Choosing file to encrypt/decrypt failed", *state, err)
- dialog.ShowError(fmt.Errorf("choosing file: %w", err), w)
- return
- }
- if reader == nil {
- logger.Log("Choosing file to encrypt/decrypt failed", *state, errors.New("no file chosen"))
- return
- }
- err = state.SetInput(reader.URI())
- logger.Log("Setting file to encrypt/decrypt", *state, err)
- if err != nil { // coverage-ignore
- dialog.ShowError(fmt.Errorf("choosing file: %w", err), w)
- }
- }
-}
-
-func MakeFilePicker(state *State, logger *Logger, w fyne.Window) *widget.Button { // coverage-ignore
- picker := widget.NewButtonWithIcon("Choose File", theme.FileIcon(), func() {
- fd := dialog.NewFileOpen(filePickerCallback(state, logger, w), w)
- fd.Show()
- })
- return picker
-}
-
-func makeComments() *widget.Entry {
- comments := widget.NewMultiLineEntry()
- comments.Validator = nil
- comments.Wrapping = fyne.TextWrapWord
- return comments
-}
-
-func keyfileText() *widget.Entry { // coverage-ignore
- text := widget.NewMultiLineEntry()
- text.Disable()
- text.SetPlaceHolder("No keyfiles added")
- return text
-}
-
-func keyfileAddCallback(state *State, logger *Logger, window fyne.Window) func(fyne.URIReadCloser, error) {
- return func(reader fyne.URIReadCloser, err error) {
- if reader != nil {
- defer reader.Close()
- }
- if err != nil { // coverage-ignore
- logger.Log("Adding keyfile failed", *state, err)
- dialog.ShowError(fmt.Errorf("adding keyfile: %w", err), window)
- return
- }
- if reader != nil {
- state.AddKeyfile(reader.URI())
- logger.Log("Adding keyfile complete", *state, nil)
- } else {
- logger.Log("Adding keyfile canceled", *state, nil)
- }
- }
-}
-
-func KeyfileAddBtn(state *State, logger *Logger, window fyne.Window) *widget.Button { // coverage-ignore
- btn := widget.NewButtonWithIcon("Add", theme.ContentAddIcon(), func() {
- fd := dialog.NewFileOpen(keyfileAddCallback(state, logger, window), window)
- fd.Show()
- })
- return btn
-}
-
-func keyfileCreateCallback(state *State, logger *Logger, window fyne.Window) func(fyne.URIWriteCloser, error) {
- return func(writer fyne.URIWriteCloser, err error) {
- if writer != nil {
- defer writer.Close()
- }
- if err != nil { // coverage-ignore
- logger.Log("Creating keyfile failed", *state, err)
- dialog.ShowError(fmt.Errorf("creating keyfile: %w", err), window)
- return
- }
- if writer != nil {
- data := make([]byte, 32)
- _, err := rand.Read(data)
- if err != nil { // coverage-ignore
- logger.Log("Creating keyfile data failed", *state, err)
- dialog.ShowError(fmt.Errorf("creating keyfile: %w", err), window)
- return
- }
- _, err = writer.Write(data)
- if err != nil { // coverage-ignore
- logger.Log("Writing keyfile failed", *state, err)
- dialog.ShowError(fmt.Errorf("writing keyfile: %w", err), window)
- return
- }
- state.AddKeyfile(writer.URI())
- logger.Log("Created keyfile", *state, nil)
- } else {
- logger.Log("Creating keyfile canceled", *state, nil)
- }
- }
-}
-
-func KeyfileCreateBtn(state *State, logger *Logger, window fyne.Window) *widget.Button { // coverage-ignore
- btn := widget.NewButtonWithIcon("Create", theme.ContentAddIcon(), func() {
- fd := dialog.NewFileSave(keyfileCreateCallback(state, logger, window), window)
- fd.SetFileName("Keyfile")
- fd.Show()
- })
- return btn
-}
-
-func keyfileClearCallback(state *State, logger *Logger) func() {
- return func() {
- logger.Log("Clearing keyfiles", *state, nil)
- state.ClearKeyfiles()
- }
-}
-
-func KeyfileClearBtn(state *State, logger *Logger) *widget.Button { // coverage-ignore
- btn := widget.NewButtonWithIcon("Clear", theme.ContentClearIcon(), keyfileClearCallback(state, logger))
- return btn
-}
-
-func makePassword() *widget.Entry {
- password := widget.NewPasswordEntry()
- password.SetPlaceHolder("Password")
- password.Validator = nil
- return password
-}
-
-func makeConfirmPassword() *widget.Entry {
- confirm := widget.NewPasswordEntry()
- confirm.SetPlaceHolder("Confirm password")
- confirm.Validator = nil
- return confirm
-}
-
-func WorkBtnCallback(state *State, logger *Logger, w fyne.Window, encrypt func(), decrypt func()) func() {
- return func() {
- if !(state.IsEncrypting() || state.IsDecrypting()) {
- // This should never happen (the button should be hidden), but check in case
- // there is a race condition
- logger.Log("Encrypt/Decrypt button pressed", *state, errors.New("button should be hidden"))
- dialog.ShowError(errors.New("no file chosen"), w)
- return
- }
- if state.IsEncrypting() {
- if state.Password.Text != state.ConfirmPassword.Text {
- logger.Log("Encrypt/Decrypt button pressed", *state, errors.New("passwords do not match"))
- dialog.ShowError(errors.New("passwords do not match"), w)
- } else if state.Password.Text == "" {
- logger.Log("Encrypt/Decrypt button pressed", *state, errors.New("password cannot be blank"))
- dialog.ShowError(errors.New("password cannot be blank"), w)
- } else {
- logger.Log("Encrypt/Decrypt button pressed (encrypting)", *state, nil)
- encrypt()
- }
- return
- }
- logger.Log("Encrypt/Decrypt button pressed (decrypting)", *state, nil)
- decrypt()
- }
-}
-
-type ByteCounter struct {
- total int64
- startTime int64
- endTime int64
-}
-
-func (bc *ByteCounter) Read(p []byte) (int, error) {
- bc.total += int64(len(p))
- if bc.startTime == 0 {
- bc.startTime = time.Now().Unix()
- }
- bc.endTime = time.Now().Unix()
- return len(p), nil
-}
-
-func (bc *ByteCounter) Write(p []byte) (int, error) {
- return bc.Read(p)
-}
-
-func (bc *ByteCounter) stringify(n float64) string {
- for i, prefix := range []string{"B", "KB", "MB", "GB"} {
- scale := 1 << (10 * i)
- if n < float64(scale<<10) {
- return fmt.Sprintf("%.2f %s", n/float64(scale), prefix)
- }
- }
- return fmt.Sprintf("%.2f TB", n/(1<<40))
-}
-
-func (bc *ByteCounter) Total() string {
- return bc.stringify(float64(bc.total))
-}
-
-func (bc *ByteCounter) Rate() string {
- elapsed := bc.endTime - bc.startTime
- if elapsed == 0 {
- return "0 B/s"
- }
- rate := float64(bc.total) / float64(elapsed)
- return bc.stringify(rate) + "/s"
-}
diff --git a/internal/ui/elements_test.go b/internal/ui/elements_test.go
deleted file mode 100644
index cd4f659..0000000
--- a/internal/ui/elements_test.go
+++ /dev/null
@@ -1,327 +0,0 @@
-package ui
-
-import (
- "log"
- "testing"
-
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/storage"
- "fyne.io/fyne/v2/test"
-)
-
-func MakeURI(name string) fyne.URI {
- uri, err := storage.ParseURI("file://" + name)
- if err != nil {
- log.Println("Error creating URI:", err)
- panic(err)
- }
- return uri
-}
-
-type TestReadWriteCloser struct {
- bytesWritten int
- bytesRead int
- isClosed bool
-}
-
-func (t *TestReadWriteCloser) Read(p []byte) (n int, err error) {
- t.bytesRead += len(p)
- return len(p), nil
-}
-
-func (t *TestReadWriteCloser) Write(p []byte) (n int, err error) {
- t.bytesWritten += len(p)
- return len(p), nil
-}
-
-func (t *TestReadWriteCloser) Close() error {
- t.isClosed = true
- return nil
-}
-
-func (t *TestReadWriteCloser) URI() fyne.URI {
- return MakeURI("test")
-}
-
-func TestWriteLogsCallback(t *testing.T) {
- logger := Logger{}
- app := test.NewApp()
- window := app.NewWindow("Test Window")
- callback := writeLogsCallback(&logger, window)
-
- test := TestReadWriteCloser{}
- callback(&test, nil)
- if !test.isClosed {
- t.Errorf("Expected TestReadWriteCloser to be closed, but it was not.")
- }
- if test.bytesWritten == 0 {
- t.Errorf("Expected some bytes to be written, but none were.")
- }
-}
-
-func TestFilePickerCallback(t *testing.T) {
- logger := Logger{}
- app := test.NewApp()
- window := app.NewWindow("Test Window")
- state := NewState(app)
- callback := filePickerCallback(state, &logger, window)
- test := TestReadWriteCloser{}
-
- // Test canceling the file picker
- if state.Input() != nil {
- t.Errorf("State should not be initialized with an input")
- }
- callback(nil, nil)
- if state.Input() != nil {
- t.Errorf("State should not be updated with an input")
- }
-
- if state.Input() != nil {
- t.Errorf("State should not be initialized with an input")
- }
- callback(&test, nil)
- if !test.isClosed {
- t.Errorf("Resource must be closed")
- }
- if test.bytesRead != 0 {
- t.Errorf("Chosen file should not be read yet")
- }
- if state.Input() == nil {
- t.Errorf("State should be updated with an input")
- }
-}
-
-func TestFilename(t *testing.T) {
- state := NewState(test.NewApp())
- if state.FileName.Text != "" {
- t.Errorf("Filename should be empty")
- }
-
- state.SetInput(MakeURI("test-filename"))
- if state.FileName.Text != "test-filename" {
- t.Errorf("Filname should match input")
- }
-
- state.SetInput(MakeURI("test-filename-2"))
- if state.FileName.Text != "test-filename-2" {
- t.Errorf("Filname should match input")
- }
-}
-
-func TestComments(t *testing.T) {
- state := NewState(test.NewApp())
-
- state.SetInput(MakeURI("test"))
- if !state.IsEncrypting() {
- t.Errorf("State should be encrypting")
- }
- if state.Comments.Text != "" {
- t.Errorf("Comments should be empty")
- }
- if state.Comments.Disabled() {
- t.Errorf("Comments should be enabled")
- }
- if state.Comments.PlaceHolder != "Comments are not encrypted" {
- t.Errorf("Comments should warn user that they are not encrypted")
- }
-
- // Choosing deniability should disable comments
- state.Deniability.SetChecked(true)
- if state.Comments.Text != "" {
- t.Errorf("Comments should be empty")
- }
- if !state.Comments.Disabled() {
- t.Errorf("Comments should be disabled")
- }
- if state.Comments.PlaceHolder != "Comments are disabled in deniability mode" {
- t.Errorf("Comments should warn user that they are disabled")
- }
- if state.Comments.Text != "" {
- t.Errorf("State should be updated with comments")
- }
-
- // Switching to decrypting mode should disable comments
- err := state.SetInput(MakeURI("test.pcv"))
- if err != nil {
- t.Errorf("Error setting input: %v", err)
- }
- if !state.IsDecrypting() {
- t.Errorf("State should be decrypting")
- }
- if state.Comments.Text != "" {
- t.Errorf("Comments should be empty")
- }
- if !state.Comments.Disabled() {
- t.Errorf("Comments should be disabled")
- }
-}
-
-func TestKeyfileAddCallback(t *testing.T) {
- app := test.NewApp()
- window := app.NewWindow("Test Window")
- state := NewState(app)
- logger := Logger{}
- callback := keyfileAddCallback(state, &logger, window)
-
- callback(nil, nil)
- reader := TestReadWriteCloser{}
- callback(&reader, nil)
- if !reader.isClosed {
- t.Errorf("Expected TestReadWriteCloser to be closed, but it was not.")
- }
- if reader.bytesRead != 0 {
- t.Errorf("Keyfile should not be read yet")
- }
- if len(state.Keyfiles) != 1 {
- t.Errorf("Expected one keyfile to be added, but got %d", len(state.Keyfiles))
- }
-}
-
-func TestKeyfileCreateCallback(t *testing.T) {
- state := NewState(test.NewApp())
- app := test.NewApp()
- window := app.NewWindow("Test Window")
- logger := Logger{}
- callback := keyfileCreateCallback(state, &logger, window)
-
- callback(nil, nil)
- reader := TestReadWriteCloser{}
- callback(&reader, nil)
- if !reader.isClosed {
- t.Errorf("Expected TestReadWriteCloser to be closed, but it was not.")
- }
- if reader.bytesWritten != 32 {
- t.Errorf("Should have written 32 bytes, but wrote %d", reader.bytesWritten)
- }
- if len(state.Keyfiles) != 1 {
- t.Errorf("Expected one keyfile to be added, but got %d", len(state.Keyfiles))
- }
-}
-
-func TestKeyfileClearCallback(t *testing.T) {
- state := NewState(test.NewApp())
- logger := Logger{}
- callback := keyfileClearCallback(state, &logger)
-
- state.AddKeyfile(MakeURI("test-keyfile"))
- if len(state.Keyfiles) != 1 {
- t.Errorf("Expected one keyfile, but got %d", len(state.Keyfiles))
- }
- callback()
- if len(state.Keyfiles) != 0 {
- t.Errorf("Expected no keyfiles, but got %d", len(state.Keyfiles))
- }
-}
-
-func TestKeyfileTextUpdate(t *testing.T) {
- state := NewState(test.NewApp())
- if state.KeyfileText.Text != "" {
- t.Errorf("Text should be empty")
- }
-
- state.AddKeyfile(MakeURI("test-keyfile-1"))
- state.AddKeyfile(MakeURI("test-keyfile-2"))
- if state.KeyfileText.Text != "test-keyfile-1\ntest-keyfile-2" {
- t.Errorf("Text should be updated to show keyfiles")
- }
-}
-
-func TestPasswordEntry(t *testing.T) {
- state := NewState(test.NewApp())
- if state.Password.Text != "" {
- t.Errorf("Password should be empty")
- }
-
- // Test enabling / disabling
- state.SetInput(MakeURI("test.pcv"))
- if state.Password.Disabled() {
- t.Errorf("Password should be enabled")
- }
- state.SetInput(MakeURI("test"))
- if state.Password.Disabled() {
- t.Errorf("Password should be enabled")
- }
-}
-
-func TestConfirmEntry(t *testing.T) {
- state := NewState(test.NewApp())
- if state.ConfirmPassword.Text != "" {
- t.Errorf("Confirm should be empty")
- }
-
- // Test enabling / disabling
- state.SetInput(MakeURI("test.pcv"))
- if !state.IsDecrypting() {
- t.Errorf("State should be decrypting")
- }
- if !state.ConfirmPassword.Disabled() {
- t.Errorf("Confirm should not be enabled for decrypting")
- }
- state.SetInput(MakeURI("test"))
- if !state.IsEncrypting() {
- t.Errorf("State should be encrypting")
- }
- if !state.ConfirmPassword.Visible() {
- t.Errorf("Confirm should be visible for encrypting")
- }
-}
-
-func TestWorkBtn(t *testing.T) {
- logger := Logger{}
- app := test.NewApp()
- window := app.NewWindow("Test Window")
- state := NewState(app)
- encryptCalled := false
- encrypt := func() {
- encryptCalled = true
- }
- decryptCalled := false
- decrypt := func() {
- decryptCalled = true
- }
- state.WorkBtn.OnTapped = WorkBtnCallback(state, &logger, window, encrypt, decrypt)
-
- // Set state to encrypting
- state.SetInput(MakeURI("test"))
- if state.WorkBtn.Disabled() {
- t.Errorf("Work button should be enabled")
- }
- if state.WorkBtn.Text != "Encrypt" {
- t.Errorf("Work button should say 'Encrypt'")
- }
-
- // Test mismatched passwords
- state.Password.Text = "test-password"
- state.ConfirmPassword.Text = "test-confirm"
- test.Tap(state.WorkBtn)
- if encryptCalled || decryptCalled {
- t.Errorf("Encrypt or decrypt should not be called")
- }
-
- // Match passwords
- state.ConfirmPassword.Text = "test-password"
- test.Tap(state.WorkBtn)
- if !encryptCalled || decryptCalled {
- t.Errorf("Only encrypt should be called")
- }
-
- // Set state to decrypting
- err := state.SetInput(MakeURI("test.pcv"))
- if err != nil {
- t.Errorf("Error setting input: %v", err)
- }
- if state.WorkBtn.Disabled() {
- t.Errorf("Work button should be visible")
- }
- if state.WorkBtn.Text != "Decrypt" {
- t.Errorf("Work button should say 'Decrypt'")
- }
-
- // Test decrypting
- encryptCalled = false
- decryptCalled = false
- test.Tap(state.WorkBtn)
- if encryptCalled || !decryptCalled {
- t.Errorf("Only decrypt should be called")
- }
-}
diff --git a/internal/ui/logger.go b/internal/ui/logger.go
deleted file mode 100644
index 6704aea..0000000
--- a/internal/ui/logger.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package ui
-
-import (
- "strconv"
- "strings"
- "sync"
- "time"
-)
-
-func redactedFile(f *fileDesc) string {
- if f == nil {
- return "null"
- }
- redactedUri := "parsing-error-" + strconv.Itoa(f.id)
- splitIdx := strings.Index(f.uri, ":")
- if splitIdx != -1 {
- redactedUri = f.uri[:splitIdx+1] + "redacted-" + strconv.Itoa(f.id)
- }
- return "{\"Uri\":\"" + redactedUri + "\"}"
-}
-
-func redactedString(s string) string {
- if len(s) > 0 {
- return "non-empty-string"
- }
- return ""
-}
-
-func stateJson(state State) string {
- keyfiles := []string{}
- for _, k := range state.Keyfiles {
- keyfiles = append(keyfiles, redactedFile(&k))
- }
- keyfilesJson := "[" + strings.Join(keyfiles, ",") + "]"
- fields := []string{
- "\"Input\":" + redactedFile(state.Input()),
- "\"SaveAs\":" + redactedFile(state.SaveAs),
- "\"Comments\":\"" + redactedString(state.Comments.Text) + "\"",
- "\"ReedSolomon\":" + strconv.FormatBool(state.ReedSolomon.Checked),
- "\"Deniability\":" + strconv.FormatBool(state.Deniability.Checked),
- "\"Paranoid\":" + strconv.FormatBool(state.Paranoid.Checked),
- "\"Keyfiles\":" + keyfilesJson,
- }
- return "{" + strings.Join(fields, ",") + "}"
-}
-
-type logLine struct {
- time string
- action string
- state string
- err string
-}
-
-type Logger struct {
- lines []logLine
- mutex sync.Mutex
-}
-
-func (l *Logger) Log(action string, state State, err error) {
- errMsg := ""
- if err != nil {
- errMsg = err.Error()
- }
- line := logLine{
- time: time.Now().Format("15:04:05.000"),
- action: action,
- state: stateJson(state),
- err: errMsg,
- }
- l.mutex.Lock()
- defer l.mutex.Unlock()
- l.lines = append(l.lines, line)
-}
-
-func (l *Logger) CsvString() string {
- logLines := []string{"Time,Action,State,Error," + PicoGoVersion}
- for _, line := range l.lines {
- fields := []string{
- "\"" + line.time + "\"",
- "\"" + line.action + "\"",
- "\"" + line.state + "\"",
- "\"" + line.err + "\"",
- }
- logLines = append(logLines, strings.Join(fields, ","))
- }
- return strings.Join(logLines, "\n")
-}
diff --git a/internal/ui/state.go b/internal/ui/state.go
deleted file mode 100644
index 8a649c2..0000000
--- a/internal/ui/state.go
+++ /dev/null
@@ -1,274 +0,0 @@
-package ui
-
-import (
- "fmt"
- "strings"
- "sync"
-
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/storage"
- "fyne.io/fyne/v2/widget"
-
- "github.com/picocrypt/picogo/internal/encryption"
-)
-
-const PicoGoVersion = "v0.1.5"
-
-var fileDescCount int
-var fileDescMutex sync.Mutex
-
-func nextFileDescID() int {
- fileDescMutex.Lock()
- defer fileDescMutex.Unlock()
- fileDescCount++
- return fileDescCount
-}
-
-type fileDesc struct {
- name string
- uri string
- id int
-}
-
-func (f *fileDesc) Name() string {
- return f.name
-}
-
-func (f *fileDesc) Uri() string {
- return f.uri
-}
-
-func NewFileDesc(uri fyne.URI) fileDesc {
- return fileDesc{
- name: uri.Name(),
- uri: uri.String(),
- id: nextFileDescID(),
- }
-}
-
-type Settings struct {
- ReedSolomonDefault *widget.Check
- ParanoidDefault *widget.Check
- OrderedKfDefault *widget.Check
- DeniabilityDefault *widget.Check
-}
-
-func (s *Settings) Save(app fyne.App) {
- preferences := app.Preferences()
- preferences.SetBool("ReedSolomonDefault", s.ReedSolomonDefault.Checked)
- preferences.SetBool("ParanoidDefault", s.ParanoidDefault.Checked)
- preferences.SetBool("OrderedKfDefault", s.OrderedKfDefault.Checked)
- preferences.SetBool("DeniabilityDefault", s.DeniabilityDefault.Checked)
-}
-
-func NewSettings(app fyne.App) *Settings {
- s := Settings{}
- s.ReedSolomonDefault = widget.NewCheck("Reed-Solomon", func(bool) { s.Save(app) })
- s.ParanoidDefault = widget.NewCheck("Paranoid", func(bool) { s.Save(app) })
- s.OrderedKfDefault = widget.NewCheck("Ordered Keyfiles", func(bool) { s.Save(app) })
- s.DeniabilityDefault = widget.NewCheck("Deniability", func(bool) { s.Save(app) })
-
- reedSolomon := app.Preferences().Bool("ReedSolomonDefault")
- orderedKf := app.Preferences().Bool("OrderedKfDefault")
- paranoid := app.Preferences().Bool("ParanoidDefault")
- deniability := app.Preferences().Bool("DeniabilityDefault")
-
- s.ReedSolomonDefault.SetChecked(reedSolomon)
- s.ParanoidDefault.SetChecked(paranoid)
- s.OrderedKfDefault.SetChecked(orderedKf)
- s.DeniabilityDefault.SetChecked(deniability)
- return &s
-}
-
-type State struct {
- FileName *widget.Label
- input *fileDesc
- SaveAs *fileDesc
- Comments *widget.Entry
- ReedSolomon *widget.Check
- Deniability *widget.Check
- Paranoid *widget.Check
- OrderedKeyfiles *widget.Check
- Keyfiles []fileDesc
- KeyfileText *widget.Entry
- Password *widget.Entry
- ConfirmPassword *widget.Entry
- WorkBtn *widget.Button
- Settings *Settings
-}
-
-func NewState(app fyne.App) *State {
- state := State{
- FileName: widget.NewLabel(""),
- input: nil,
- SaveAs: nil,
- Comments: makeComments(),
- ReedSolomon: widget.NewCheck("Reed-Solomon", nil),
- Deniability: widget.NewCheck("Deniability", nil),
- Paranoid: widget.NewCheck("Paranoid", nil),
- OrderedKeyfiles: widget.NewCheck("Require correct order", nil),
- Keyfiles: []fileDesc{},
- KeyfileText: keyfileText(),
- Password: makePassword(),
- ConfirmPassword: makeConfirmPassword(),
- WorkBtn: widget.NewButton("Encrypt", nil),
- Settings: NewSettings(app),
- }
-
- state.FileName.Wrapping = fyne.TextWrap(fyne.TextTruncateEllipsis)
- state.Deniability.OnChanged = func(checked bool) { state.updateComments() }
-
- state.ReedSolomon.SetChecked(state.Settings.ReedSolomonDefault.Checked)
- state.Paranoid.SetChecked(state.Settings.ParanoidDefault.Checked)
- state.OrderedKeyfiles.SetChecked(state.Settings.OrderedKfDefault.Checked)
- state.Deniability.SetChecked(state.Settings.DeniabilityDefault.Checked)
-
- return &state
-}
-
-func (s *State) updateComments() {
- if s.Deniability.Checked {
- s.Comments.SetText("")
- s.Comments.SetPlaceHolder("Comments are disabled in deniability mode")
- s.Comments.Disable()
- } else {
- s.Comments.SetPlaceHolder("Comments are not encrypted")
- s.Comments.Enable()
- }
-}
-
-func (s *State) Input() *fileDesc {
- return s.input
-}
-
-func (s *State) IsEncrypting() bool {
- if s.input == nil {
- return false
- }
- return !strings.HasSuffix(s.input.Name(), ".pcv")
-}
-
-func (s *State) IsDecrypting() bool {
- if s.input == nil {
- return false
- }
- return strings.HasSuffix(s.input.Name(), ".pcv")
-}
-
-func (s *State) SetInput(input fyne.URI) error {
- inputDesc := NewFileDesc(input)
- s.input = &inputDesc
-
- settings := encryption.Settings{
- Comments: "",
- ReedSolomon: s.Settings.ReedSolomonDefault.Checked,
- Deniability: s.Settings.DeniabilityDefault.Checked,
- Paranoid: s.Settings.ParanoidDefault.Checked,
- }
- if s.IsDecrypting() {
- reader, err := storage.Reader(input)
- if reader != nil {
- defer reader.Close()
- }
- if err != nil {
- return fmt.Errorf("failed to open file: %w", err)
- }
- settings, err = encryption.GetEncryptionSettings(reader)
- if err != nil {
- return fmt.Errorf("failed to get encryption settings: %w", err)
- }
- }
-
- s.FileName.SetText(s.input.Name())
-
- // Set Deniability before Comments to overwrite the result of the callback
- s.ReedSolomon.SetChecked(settings.ReedSolomon)
- s.Deniability.SetChecked(settings.Deniability)
- s.Paranoid.SetChecked(settings.Paranoid)
- if s.IsEncrypting() {
- s.ReedSolomon.Enable()
- s.Deniability.Enable()
- s.Paranoid.Enable()
- } else {
- s.ReedSolomon.Disable()
- s.Deniability.Disable()
- s.Paranoid.Disable()
- }
-
- s.Comments.SetText(settings.Comments)
- if s.IsEncrypting() {
- if settings.Deniability {
- s.Comments.SetText("")
- s.Comments.SetPlaceHolder("Comments are disabled in deniability mode")
- s.Comments.Disable()
- } else {
- s.Comments.SetPlaceHolder("Comments are not encrypted")
- s.Comments.Enable()
- }
- } else {
- s.Comments.SetPlaceHolder("")
- s.Comments.SetPlaceHolder("")
- s.Comments.Disable()
- }
-
- s.OrderedKeyfiles.Enable()
-
- if s.IsEncrypting() {
- s.ConfirmPassword.SetPlaceHolder("Confirm password")
- s.ConfirmPassword.Enable()
- } else {
- s.ConfirmPassword.SetPlaceHolder("Not required")
- s.ConfirmPassword.Disable()
- }
-
- if s.IsEncrypting() {
- s.WorkBtn.SetText("Encrypt")
- s.WorkBtn.Enable()
- } else {
- s.WorkBtn.SetText("Decrypt")
- s.WorkBtn.Enable()
- }
-
- return nil
-}
-
-func (s *State) AddKeyfile(uri fyne.URI) {
- s.Keyfiles = append(s.Keyfiles, NewFileDesc(uri))
- names := []string{}
- for _, kf := range s.Keyfiles {
- names = append(names, kf.Name())
- }
- msg := strings.Join(names, "\n")
- fyne.Do(func() { s.KeyfileText.SetText(msg) })
-}
-
-func (s *State) ClearKeyfiles() {
- s.Keyfiles = []fileDesc{}
- fyne.Do(func() { s.KeyfileText.SetText("No keyfiles added") })
-}
-
-func (s *State) Clear() {
- s.input = nil
- s.SaveAs = nil
- s.Keyfiles = nil
- fyne.DoAndWait(func() {
- s.FileName.SetText("")
- s.Comments.SetText("")
- s.ReedSolomon.SetChecked(s.Settings.ReedSolomonDefault.Checked)
- s.Deniability.SetChecked(s.Settings.DeniabilityDefault.Checked)
- s.Paranoid.SetChecked(s.Settings.ParanoidDefault.Checked)
- s.OrderedKeyfiles.SetChecked(s.Settings.OrderedKfDefault.Checked)
- s.Password.SetText("")
- s.ConfirmPassword.SetText("")
- })
-}
-
-func (s *State) DefaultSaveName() string {
- if s.input == nil {
- return ""
- }
- if s.IsEncrypting() {
- return s.input.Name() + ".pcv"
- }
- return strings.TrimSuffix(s.input.Name(), ".pcv")
-}
diff --git a/internal/ui/test.pcv b/internal/ui/test.pcv
deleted file mode 100644
index 070fb5f..0000000
Binary files a/internal/ui/test.pcv and /dev/null differ
diff --git a/internal/ui/theme.go b/internal/ui/theme.go
deleted file mode 100644
index 74bf9ef..0000000
--- a/internal/ui/theme.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package ui
-
-import (
- "image/color"
-
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/theme"
-)
-
-type Theme struct {
- Scale float32
-}
-
-func (t Theme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
- switch name {
- case theme.ColorNameDisabled:
- return color.NRGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff}
- default:
- return theme.DarkTheme().Color(name, variant)
- }
-}
-func (t Theme) Font(style fyne.TextStyle) fyne.Resource {
- return theme.DarkTheme().Font(style)
-}
-func (t Theme) Icon(name fyne.ThemeIconName) fyne.Resource {
- return theme.DarkTheme().Icon(name)
-}
-func (t Theme) Size(name fyne.ThemeSizeName) float32 {
- return theme.DarkTheme().Size(name) * t.Scale
-}
-
-func NewTheme(scale float32) Theme {
- return Theme{Scale: scale}
-}
diff --git a/picogo.go b/picogo.go
deleted file mode 100644
index a7c8b87..0000000
--- a/picogo.go
+++ /dev/null
@@ -1,660 +0,0 @@
-package main
-
-import (
- "bytes"
- "errors"
- "fmt"
- "image/color"
- "io"
- "strconv"
- "strings"
- "time"
-
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/app"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/container"
- "fyne.io/fyne/v2/dialog"
- "fyne.io/fyne/v2/layout"
- "fyne.io/fyne/v2/storage"
- "fyne.io/fyne/v2/widget"
-
- "github.com/picocrypt/picogo/internal/encryption"
- "github.com/picocrypt/picogo/internal/ui"
-)
-
-const (
- previewTextSize = 5000
-)
-
-func uriReadCloser(uri string) (fyne.URIReadCloser, error) {
- fullUri, err := storage.ParseURI(uri)
- if err != nil {
- return nil, err
- }
- return storage.Reader(fullUri)
-}
-
-func uriWriteCloser(uri string) (fyne.URIWriteCloser, error) {
- fullUri, err := storage.ParseURI(uri)
- if err != nil {
- return nil, err
- }
- return storage.Writer(fullUri)
-}
-
-func clearFile(uri fyne.URI) error {
- writer, err := storage.Writer(uri)
- if writer != nil {
- defer writer.Close()
- }
- if err != nil {
- return err
- }
- _, err = writer.Write(make([]byte, 1))
- return err
-}
-
-func getOutputURI(app fyne.App) (fyne.URI, error) {
- return storage.Child(app.Storage().RootURI(), "output")
-}
-
-func outputReader(app fyne.App) (io.ReadCloser, error) {
- outputURI, err := getOutputURI(app)
- if err != nil {
- return nil, fmt.Errorf("finding output file: %w", err)
- }
- output, err := uriReadCloser(outputURI.String())
- if err != nil {
- return nil, fmt.Errorf("opening output file: %w", err)
- }
- return output, nil
-}
-
-func clearOutputFile(app fyne.App) error {
- outputURI, err := getOutputURI(app)
- if err != nil {
- return err
- }
- return clearFile(outputURI)
-}
-
-func chooseSaveAs(logger *ui.Logger, state *ui.State, window fyne.Window, app fyne.App, prepend []byte) {
- d := dialog.NewFileSave(func(writer fyne.URIWriteCloser, err error) {
- if writer != nil {
- defer writer.Close()
- }
- if err != nil {
- logger.Log("Failed while choosing where to save output", *state, err)
- dialog.ShowError(fmt.Errorf("choosing output file: %w", err), window)
- return
- }
- if writer != nil {
- saveAs := ui.NewFileDesc(writer.URI())
- state.SaveAs = &saveAs
- logger.Log("Chose where to save output", *state, nil)
- go func() {
- saveOutput(logger, state, window, app, prepend)
- }()
- } else {
- logger.Log("Canceled choosing where to save output", *state, nil)
- }
- }, window)
- if input := state.Input(); input == nil {
- logger.Log("Failed to seed filename", *state, errors.New("input is nil"))
- } else {
- if state.IsEncrypting() {
- d.SetFileName(input.Name() + ".pcv")
- } else {
- d.SetFileName(input.Name()[:len(state.Input().Name())-4])
-
- }
- }
- fyne.Do(d.Show)
-}
-
-func saveOutput(logger *ui.Logger, state *ui.State, window fyne.Window, app fyne.App, prepend []byte) {
- defer func() { state.SaveAs = nil }()
- outputURI, err := getOutputURI(app)
- if err != nil {
- logger.Log("Get output uri", *state, err)
- dialog.ShowError(fmt.Errorf("finding output file: %w", err), window)
- return
- }
- output, err := storage.Reader(outputURI)
- if output != nil {
- defer output.Close()
- }
- if err != nil {
- logger.Log("Get output reader", *state, err)
- dialog.ShowError(fmt.Errorf("opening output file: %w", err), window)
- return
- }
- saveAs, err := uriWriteCloser(state.SaveAs.Uri())
- if saveAs != nil {
- defer saveAs.Close()
- }
- if err != nil {
- logger.Log("Get save as writer", *state, err)
- dialog.ShowError(fmt.Errorf("opening save-as file: %w", err), window)
- return
- }
- errCh := make(chan error)
- counter := ui.ByteCounter{}
- go func() {
- src := io.MultiReader(bytes.NewBuffer(prepend), output)
- _, err := io.Copy(io.MultiWriter(&counter, saveAs), src)
- errCh <- err
- }()
-
- progress := widget.NewLabel("")
- d := dialog.NewCustomWithoutButtons(
- "Saving",
- container.New(layout.NewVBoxLayout(), progress),
- window,
- )
- fyne.Do(d.Show)
-
- // Block until completion
- for {
- select {
- case err := <-errCh:
- fyne.Do(d.Dismiss)
- if err != nil {
- logger.Log("Saving output", *state, err)
- dialog.ShowError(fmt.Errorf("copying output: %w", err), window)
- }
- err = clearOutputFile(app)
- if err != nil {
- logger.Log("Clearing output file", *state, err)
- dialog.ShowError(fmt.Errorf("cleaning tmp file: %w", err), window)
- }
- state.Clear()
- return
- default:
- time.Sleep(time.Second / 4)
- fyne.Do(func() {
- progress.SetText("Total: " + counter.Total() + "\nRate: " + counter.Rate())
- })
- }
- }
-}
-
-type encryptResult struct {
- header []byte
- err error
-}
-
-func encrypt(logger *ui.Logger, state *ui.State, win fyne.Window, app fyne.App) {
- counter := ui.ByteCounter{}
- resultCh := make(chan encryptResult)
-
- go func() {
- logger.Log("Start encryption routine", *state, nil)
- input, err := uriReadCloser(state.Input().Uri())
- if input != nil {
- defer input.Close()
- }
- if err != nil {
- logger.Log("Get input reader", *state, err)
- resultCh <- encryptResult{nil, fmt.Errorf("getting input file: %w", err)}
- return
- }
-
- outputURI, err := getOutputURI(app)
- if err != nil {
- logger.Log("Get output uri", *state, err)
- resultCh <- encryptResult{nil, fmt.Errorf("finding output file: %w", err)}
- return
- }
- output, err := storage.Writer(outputURI)
- if output != nil {
- defer output.Close()
- }
- if err != nil {
- logger.Log("Get output writer", *state, err)
- resultCh <- encryptResult{nil, fmt.Errorf("opening output file: %w", err)}
- return
- }
-
- keyfiles := []io.Reader{}
- for i := 0; i < len(state.Keyfiles); i++ {
- r, err := uriReadCloser(state.Keyfiles[i].Uri())
- if r != nil {
- defer r.Close()
- }
- if err != nil {
- logger.Log("Get keyfile reader "+strconv.Itoa(i), *state, err)
- resultCh <- encryptResult{nil, fmt.Errorf("getting keyfile %d: %w", i, err)}
- return
- }
- keyfiles = append(keyfiles, r)
- }
- settings := encryption.Settings{
- Comments: state.Comments.Text,
- ReedSolomon: state.ReedSolomon.Checked,
- Paranoid: state.Paranoid.Checked,
- OrderedKf: state.OrderedKeyfiles.Checked,
- Deniability: state.Deniability.Checked,
- }
- header, err := encryption.EncryptHeadless(
- input, state.Password.Text, keyfiles, settings, io.MultiWriter(output, &counter),
- )
- if err != nil {
- logger.Log("Encrypt headless", *state, err)
- resultCh <- encryptResult{nil, fmt.Errorf("encrypting: %w", err)}
- return
- }
- resultCh <- encryptResult{
- header: header,
- err: nil,
- }
- }()
-
- progress := widget.NewLabel("")
- d := dialog.NewCustomWithoutButtons("Encrypting", container.New(layout.NewVBoxLayout(), progress), win)
- fyne.Do(d.Show)
- var result *encryptResult
- for {
- select {
- case r := <-resultCh:
- fyne.Do(d.Dismiss)
- result = &r
- default:
- time.Sleep(time.Second / 4)
- fyne.Do(func() { progress.SetText("Total: " + counter.Total() + "\nRate: " + counter.Rate()) })
- }
- if result != nil {
- break
- }
- }
- logger.Log("Complete encryption", *state, result.err)
- if result.err != nil {
- dialog.ShowError(fmt.Errorf("encrypting: %w", result.err), win)
- return
- }
- text := widget.NewLabel(state.Input().Name() + " has been encrypted.")
- text.Wrapping = fyne.TextWrapWord
- fyne.Do(func() {
- dialog.ShowCustomConfirm(
- "Encryption Complete",
- "Save",
- "Cancel",
- text,
- func(b bool) {
- if b {
- chooseSaveAs(logger, state, win, app, result.header)
- }
- },
- win,
- )
- })
-}
-
-func tryDecrypt(
- logger *ui.Logger,
- state *ui.State,
- recoveryMode bool,
- w fyne.Window,
- app fyne.App,
-) (bool, error) {
- input, err := uriReadCloser(state.Input().Uri())
- if err != nil {
- logger.Log("Get input reader", *state, err)
- return false, err
- }
- defer input.Close()
-
- keyfiles := []io.Reader{}
- for i := 0; i < len(state.Keyfiles); i++ {
- r, err := uriReadCloser(state.Keyfiles[i].Uri())
- if err != nil {
- logger.Log("Get keyfile reader "+strconv.Itoa(i), *state, err)
- return false, err
- }
- defer r.Close()
- keyfiles = append(keyfiles, r)
- }
-
- outputURI, err := getOutputURI(app)
- if err != nil {
- logger.Log("Get output URI", *state, err)
- return false, err
- }
- output, err := uriWriteCloser(outputURI.String())
- if err != nil {
- logger.Log("Get output writer", *state, err)
- return false, err
- }
- defer output.Close()
-
- errCh := make(chan struct {
- bool
- error
- })
- counter := ui.ByteCounter{}
- go func() {
- logger.Log("Decryption routine start", *state, nil)
- damaged, err := encryption.Decrypt(
- state.Password.Text,
- keyfiles,
- input,
- io.MultiWriter(output, &counter),
- recoveryMode,
- )
- errCh <- struct {
- bool
- error
- }{damaged, err}
- }()
-
- // Block until completion
- progress := widget.NewLabel("")
- d := dialog.NewCustomWithoutButtons("Decrypting", container.New(layout.NewVBoxLayout(), progress), w)
- fyne.Do(d.Show)
- for {
- select {
- case err := <-errCh:
- fyne.Do(d.Dismiss)
- logger.Log("Decryption routine end", *state, err.error)
- return err.bool, err.error
- default:
- time.Sleep(time.Second / 4)
- fyne.Do(func() {
- progress.SetText("Total: " + counter.Total() + "\nRate: " + counter.Rate())
- })
- }
- }
-}
-
-func decrypt(logger *ui.Logger, state *ui.State, win fyne.Window, app fyne.App) {
- damaged, err := tryDecrypt(logger, state, false, win, app)
- recoveryMode := false
- recoveryCanceled := false
- if errors.Is(err, encryption.ErrBodyCorrupted) {
- // Offer to try again in recovery mode
- text := widget.NewLabel("The file is damaged. Would you like to try again in recovery mode?")
- text.Wrapping = fyne.TextWrapWord
- doneCh := make(chan struct{})
- dialog.ShowCustomConfirm(
- "Damaged File",
- "Recover",
- "Cancel",
- text,
- func(b bool) {
- if b {
- logger.Log("Retrying decryption in recovery mode", *state, nil)
- recoveryMode = true
- damaged, err = tryDecrypt(logger, state, true, win, app)
- } else {
- recoveryCanceled = true
- }
- doneCh <- struct{}{}
- },
- win,
- )
- <-doneCh
- }
- if recoveryCanceled {
- return
- }
-
- logger.Log("Handle decryption result (damaged:"+strconv.FormatBool(damaged)+")", *state, err)
- msg := ""
- save := false
- if err == nil && !damaged {
- msg = state.Input().Name() + " has been decrypted."
- save = true
- } else if err == nil && damaged {
- msg = state.Input().Name() + " has been decrypted successfully, but it is damaged. Consider re-encryting and replacing the damaged file."
- save = true
- } else {
- switch {
- case errors.Is(err, encryption.ErrBodyCorrupted):
- if recoveryMode {
- msg = "The file is too damaged to recover. Would you like to save the partially recovered file?"
- save = true
- } else {
- msg = "The file is too damaged to recover."
- save = false
- }
- case errors.Is(err, encryption.ErrIncorrectKeyfiles):
- msg = "One or more keyfiles are incorrect."
- save = false
- case errors.Is(err, encryption.ErrIncorrectPassword):
- msg = "The password is incorrect."
- save = false
- case errors.Is(err, encryption.ErrKeyfilesNotRequired):
- msg = "Keyfiles are not required to decrypt this file. Please remove them and try again."
- save = false
- case errors.Is(err, encryption.ErrKeyfilesRequired):
- msg = "Keyfiles are required to decrypt this file. Please add them and try again."
- save = false
- default:
- msg = "Error while decrypting: " + err.Error()
- }
- }
- if save {
- text := widget.NewLabel(msg)
- text.Wrapping = fyne.TextWrapWord
- cancelBtn := widget.NewButton("Cancel", func() {})
- previewBtn := widget.NewButton("Preview", func() { showPreview(app, state, win, logger) })
- saveBtn := widget.NewButton("Save", func() {})
- d := dialog.NewCustomWithoutButtons(
- "Decryption Complete",
- container.New(
- layout.NewVBoxLayout(),
- text,
- container.New(layout.NewHBoxLayout(), cancelBtn, previewBtn, saveBtn),
- ),
- win,
- )
- cancelBtn.OnTapped = func() { fyne.Do(d.Dismiss) }
- saveBtn.OnTapped = func() {
- fyne.Do(d.Dismiss)
- go func() { chooseSaveAs(logger, state, win, app, []byte{}) }()
- }
- fyne.Do(d.Show)
- } else {
- dialog.ShowError(errors.New(msg), win)
- }
-}
-
-func wrapWithBorder(c fyne.CanvasObject) *fyne.Container {
- border := canvas.NewRectangle(color.White)
- border.FillColor = color.Transparent
- border.StrokeColor = color.White
- border.StrokeWidth = 1
- return container.New(
- layout.NewStackLayout(),
- border,
- container.NewPadded(c),
- )
-}
-
-var developmentWarningShown = false
-
-func developmentWarning(win fyne.Window) {
- if !developmentWarningShown {
- dialog.ShowInformation(
- "Warning",
- "This app is in early development and has not been thoroughly tested",
- win,
- )
- developmentWarningShown = true
- }
-}
-
-func main() {
- a := app.New()
- theme := ui.NewTheme(1.0)
- a.Settings().SetTheme(theme)
- w := a.NewWindow("PicoGo")
- state := ui.NewState(a)
-
- logger := ui.Logger{}
- logger.Log("Starting PicoGo", *state, nil)
-
- infoBtn := ui.MakeInfoBtn(w)
- logBtn := ui.MakeLogBtn(&logger, w)
- info_row := container.New(
- layout.NewHBoxLayout(),
- infoBtn,
- logBtn,
- ui.MakeSettingsBtn(state.Settings, w),
- layout.NewSpacer(),
- )
-
- picker := ui.MakeFilePicker(state, &logger, w)
- fileRow := wrapWithBorder(
- container.New(
- layout.NewFormLayout(),
- widget.NewLabel("File"), container.NewPadded(container.NewPadded(state.FileName)),
- widget.NewLabel("Comments"), container.NewPadded(container.NewPadded(state.Comments)),
- ),
- )
-
- // Advanced encryption settings
- advSettingsRow := wrapWithBorder(
- container.New(
- layout.NewVBoxLayout(),
- container.New(
- layout.NewVBoxLayout(),
- widget.NewRichTextFromMarkdown("### Settings"),
- container.New(
- layout.NewHBoxLayout(),
- state.ReedSolomon,
- state.Paranoid,
- state.Deniability,
- ),
- ),
- ),
- )
- keyfiles := wrapWithBorder(
- container.New(
- layout.NewVBoxLayout(),
- widget.NewRichTextFromMarkdown("### Keyfiles"),
- container.NewPadded(
- container.NewBorder(
- nil,
- nil,
- container.New(
- layout.NewVBoxLayout(),
- ui.KeyfileAddBtn(state, &logger, w),
- ui.KeyfileCreateBtn(state, &logger, w),
- ui.KeyfileClearBtn(state, &logger),
- ),
- nil,
- container.NewPadded(
- container.New(
- layout.NewVBoxLayout(),
- state.OrderedKeyfiles,
- state.KeyfileText,
- ),
- ),
- ),
- ),
- ),
- )
-
- passwordRow := wrapWithBorder(
- container.NewPadded(container.NewPadded(
- container.New(
- layout.NewVBoxLayout(),
- state.Password,
- state.ConfirmPassword,
- ),
- )),
- )
-
- state.WorkBtn.OnTapped = ui.WorkBtnCallback(
- state,
- &logger,
- w,
- func() { go func() { encrypt(&logger, state, w, a) }() },
- func() { go func() { decrypt(&logger, state, w, a) }() },
- )
-
- body := container.New(
- layout.NewVBoxLayout(),
- info_row,
- picker,
- fileRow,
- passwordRow,
- advSettingsRow,
- keyfiles,
- state.WorkBtn,
- )
- minSize := fyne.NewSize(body.MinSize().Width*1.05, body.MinSize().Height*1.05)
- w.SetContent(container.NewScroll(container.New(layout.NewVBoxLayout(), body)))
-
- go func() {
- for {
- // On android, users can change device settings that will scale the size
- // of the widgets. This can lead to parts of the app going off screen.
- // To work around this, continuously check the size of the window and scale
- // the theme to fit.
- // - continuous checking is required for desktop because the user may change
- // the window size. On android, the app is always full screen, so this should
- // only have effect the first loop.
- // - only update the theme if the scale is more than 1% different from the current
- // scale. This is to prevent constant updates to the theme.
- time.Sleep(time.Second / 10.0)
- currentSize := w.Canvas().Content().Size()
- targetScale := currentSize.Width / minSize.Width
- currentScale := theme.Scale
- if targetScale > currentScale*1.01 || targetScale < currentScale*0.99 {
- theme.Scale = targetScale
- fyne.Do(func() { a.Settings().SetTheme(theme) })
- }
- }
- }()
- fyne.Do(func() { developmentWarning(w) })
- w.ShowAndRun()
-}
-
-func showImagePreview(app fyne.App, state *ui.State, window fyne.Window, logger *ui.Logger) {
- output, err := outputReader(app)
- if err != nil {
- dialog.ShowError(fmt.Errorf("opening output file: %w", err), window)
- logger.Log("Failed to open output file", *state, err)
- return
- }
- preview := app.NewWindow("Preview")
- image := canvas.NewImageFromReader(output, state.Input().Name())
- image.FillMode = canvas.ImageFillContain
- preview.SetContent(image)
- fyne.Do(preview.Show)
-}
-
-func showTextPreview(app fyne.App, state *ui.State, window fyne.Window, logger *ui.Logger) {
- output, err := outputReader(app)
- if err != nil {
- dialog.ShowError(fmt.Errorf("opening output file: %w", err), window)
- logger.Log("Failed to open output file", *state, err)
- return
- }
- // Only read the N bytes to avoid loading too much data into memory
- rawText, err := io.ReadAll(io.LimitReader(output, previewTextSize))
- if err != nil {
- dialog.ShowError(fmt.Errorf("reading output file: %w", err), window)
- logger.Log("Failed to read output file", *state, err)
- return
- }
- text := widget.NewRichTextWithText(string(rawText))
- text.Wrapping = fyne.TextWrapWord
- preview := app.NewWindow("Preview")
- preview.SetContent(container.NewVScroll(text))
- fyne.Do(preview.Show)
-}
-
-func showPreview(app fyne.App, state *ui.State, window fyne.Window, logger *ui.Logger) {
- name := state.DefaultSaveName()
- for _, suffix := range []string{".png", ".jpg", ".jpeg", ".svg"} {
- if strings.HasSuffix(name, suffix) {
- showImagePreview(app, state, window, logger)
- return
- }
- }
- showTextPreview(app, state, window, logger)
-}
diff --git a/picogo_test.go b/picogo_test.go
deleted file mode 100644
index 3094f03..0000000
--- a/picogo_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package main
-
-import (
- "bytes"
- "io"
- "os"
- "testing"
-
- "github.com/picocrypt/picogo/internal/ui"
-)
-
-func TestVersion(t *testing.T) {
- r, err := os.Open("VERSION")
- if err != nil {
- t.Fatal(err)
- }
- defer r.Close()
- version, err := io.ReadAll(r)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(version, []byte(ui.PicoGoVersion)) {
- t.Fatal(version, "does not match", []byte(ui.PicoGoVersion), "for version", ui.PicoGoVersion)
- }
-}