Compare commits
348 Commits
update/upg
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a00267a0ac | ||
![]() |
7fccb6cb16 | ||
![]() |
319f5d3113 | ||
![]() |
b7276d1364 | ||
![]() |
58429ce0c4 | ||
![]() |
72a72975e0 | ||
![]() |
57be7af031 | ||
![]() |
5004a69bff | ||
![]() |
9628f2a1b8 | ||
![]() |
4cfdcb0f95 | ||
![]() |
93d1eb9141 | ||
![]() |
96f5953ed1 | ||
![]() |
48b21b1006 | ||
![]() |
c7cdf5afcb | ||
![]() |
6d19a09c20 | ||
![]() |
079756a2d7 | ||
![]() |
9c43136e18 | ||
![]() |
38da94e6dd | ||
![]() |
da245f3f88 | ||
![]() |
1e18763f8d | ||
![]() |
82c3b0546c | ||
![]() |
f246726ab7 | ||
![]() |
331daa7e12 | ||
![]() |
9be215c295 | ||
![]() |
bb97a023de | ||
![]() |
edb4907bf5 | ||
![]() |
a718c7ed7e | ||
![]() |
4f11789f26 | ||
![]() |
eb15861b13 | ||
![]() |
8c2c8049c8 | ||
![]() |
43c167073e | ||
![]() |
1037e3306c | ||
![]() |
08e818d4dc | ||
![]() |
1946837277 | ||
![]() |
2f3761b0a7 | ||
![]() |
260bc8adb2 | ||
![]() |
87e979635b | ||
![]() |
0c9c163755 | ||
![]() |
74ceea562b | ||
![]() |
4cd91ed228 | ||
![]() |
de4a18dfa1 | ||
![]() |
c6a5362ecb | ||
![]() |
f2c1dc00a1 | ||
![]() |
f655a89be0 | ||
![]() |
0d6f837578 | ||
![]() |
8c61b028b7 | ||
![]() |
a3d5273285 | ||
![]() |
93cf4f790c | ||
![]() |
cd323c0a22 | ||
![]() |
99ca8bf953 | ||
![]() |
d40b3dc15c | ||
![]() |
057a7e2f7a | ||
![]() |
1b73c536d5 | ||
![]() |
2593a8548f | ||
![]() |
13306a9076 | ||
![]() |
cfb5098b38 | ||
![]() |
d04d455fbd | ||
![]() |
b83c4b3772 | ||
![]() |
f03c11381e | ||
![]() |
8a03ddb7f3 | ||
![]() |
913222d7cb | ||
![]() |
3e6d86c206 | ||
![]() |
d6ce2a32e9 | ||
![]() |
21f1c8404c | ||
![]() |
16ff81cf81 | ||
![]() |
0636f687ac | ||
![]() |
60d8900473 | ||
![]() |
4b2d2ef768 | ||
![]() |
48899ba9a0 | ||
![]() |
092b8689b8 | ||
![]() |
3d3ba45cb1 | ||
![]() |
b380100307 | ||
![]() |
4c5ddd59d8 | ||
![]() |
5ed183f318 | ||
![]() |
9c27fd3e78 | ||
![]() |
62c361feb0 | ||
![]() |
0ee2495528 | ||
![]() |
7dc635a625 | ||
![]() |
e15112c3b4 | ||
![]() |
1d85fffb18 | ||
![]() |
5e2f29e737 | ||
![]() |
89a09ea142 | ||
![]() |
628c0d2c19 | ||
![]() |
584feabe44 | ||
![]() |
ae2cfde897 | ||
![]() |
42c66670b8 | ||
![]() |
288539b902 | ||
![]() |
61fd32f121 | ||
![]() |
43108ec4a6 | ||
![]() |
51089c6b98 | ||
![]() |
cf18fcf91c | ||
![]() |
da0513c768 | ||
![]() |
37f520cdbe | ||
![]() |
c98572bee0 | ||
![]() |
b1774ffc4b | ||
![]() |
57aaa0c4cd | ||
![]() |
b961ae1b33 | ||
![]() |
5e418e2b1b | ||
![]() |
6d22a213f3 | ||
![]() |
a76addc43f | ||
![]() |
1d96217713 | ||
![]() |
d2b8fdcfff | ||
![]() |
426fbc2510 | ||
![]() |
e89a961c02 | ||
![]() |
766c29b7a9 | ||
![]() |
507b671448 | ||
![]() |
3118ffaeb5 | ||
![]() |
0abe29bd77 | ||
![]() |
f3a7831390 | ||
![]() |
37cd58f7ba | ||
![]() |
7dd80a8ef7 | ||
![]() |
c78636264b | ||
![]() |
035506a5a3 | ||
![]() |
4cf46ef062 | ||
![]() |
c7b8063171 | ||
![]() |
0a8b149c9a | ||
![]() |
9240a27791 | ||
![]() |
e90d5b903c | ||
![]() |
50b4a9f1b9 | ||
![]() |
9783c3b5fe | ||
![]() |
7a837e3237 | ||
![]() |
c8f6714373 | ||
![]() |
bc0313aa6a | ||
![]() |
0f98668bcd | ||
![]() |
c4206e58bf | ||
![]() |
630ededf3b | ||
![]() |
8d1195ac96 | ||
![]() |
df731ac1b3 | ||
![]() |
bd784fa13d | ||
![]() |
a6bc5e657c | ||
![]() |
56c4cdb321 | ||
![]() |
42a4a83c7d | ||
![]() |
fe3933e154 | ||
![]() |
3efe130ee8 | ||
![]() |
5808857749 | ||
![]() |
a7397c3316 | ||
![]() |
d12f936898 | ||
![]() |
67f7d74bb9 | ||
![]() |
b0d0f06073 | ||
![]() |
67ee571c27 | ||
![]() |
a360695271 | ||
![]() |
149857a516 | ||
![]() |
fb8ffb802f | ||
![]() |
e957073457 | ||
![]() |
da86b0f50b | ||
![]() |
0aa78ffd66 | ||
![]() |
ce0087af99 | ||
![]() |
576bfeecfe | ||
![]() |
c0e2f34b79 | ||
![]() |
84d0c32610 | ||
![]() |
40184dbd55 | ||
![]() |
2d17bdde19 | ||
![]() |
e2babde1fa | ||
![]() |
e1f26fb045 | ||
![]() |
adbbfa0ac1 | ||
![]() |
37a013135e | ||
![]() |
acc6ea7f85 | ||
![]() |
62f012713a | ||
![]() |
0d726c1789 | ||
![]() |
f162e868b9 | ||
![]() |
0a1f95653f | ||
![]() |
27451825c6 | ||
![]() |
bdd6f1033e | ||
![]() |
c0413f9b74 | ||
![]() |
59d6fc8fdb | ||
![]() |
c500245647 | ||
![]() |
026a263f10 | ||
![]() |
839e6d3cb4 | ||
![]() |
735f4caf89 | ||
![]() |
11af71ef82 | ||
![]() |
e09577d17f | ||
![]() |
c1dbf171f5 | ||
![]() |
4ca4ec10be | ||
![]() |
77fded4964 | ||
![]() |
578491b1c7 | ||
![]() |
eee3ffd861 | ||
![]() |
89696d7f0d | ||
![]() |
e5d28f0979 | ||
![]() |
0e581a66c5 | ||
![]() |
a202c76bf0 | ||
![]() |
c9936ab76b | ||
![]() |
7ac6f7ed51 | ||
![]() |
ceb31c54b1 | ||
![]() |
42d8be593e | ||
![]() |
313adb6c3e | ||
![]() |
668ba4cdee | ||
![]() |
a36bfa7ff5 | ||
![]() |
26c37bcd2a | ||
![]() |
1980f05a7c | ||
![]() |
dbf10ba9fb | ||
![]() |
4be18d8373 | ||
![]() |
831b290d81 | ||
![]() |
9d4c15f7bc | ||
![]() |
4c4afa792d | ||
![]() |
8e256ac94d | ||
![]() |
ba7b02cd1e | ||
![]() |
65ff09f866 | ||
![]() |
aec9441de4 | ||
![]() |
8e9c2824cf | ||
![]() |
92b8ff5c8d | ||
![]() |
223c3bfb8e | ||
![]() |
b4e03a8374 | ||
![]() |
fb2df35d37 | ||
![]() |
345dad5d04 | ||
![]() |
50d6598b02 | ||
![]() |
90f04b76f4 | ||
![]() |
8b4314c394 | ||
![]() |
17241bc422 | ||
![]() |
c4a73bf107 | ||
![]() |
e76f3999b6 | ||
![]() |
5edf42254d | ||
![]() |
0567bfe645 | ||
![]() |
6043bdbc48 | ||
![]() |
4ba40ba24f | ||
![]() |
e2711b709d | ||
![]() |
4764b15e75 | ||
![]() |
1b389ef12e | ||
![]() |
b32c2dbc7e | ||
![]() |
f06937dab3 | ||
![]() |
14efce62ff | ||
![]() |
3c8b530e2e | ||
![]() |
9939e07b7d | ||
![]() |
ecf416febc | ||
![]() |
f949e6e389 | ||
![]() |
15bf08f5e6 | ||
![]() |
fa69f38ab1 | ||
![]() |
d3b06080eb | ||
![]() |
749ab330ff | ||
![]() |
677c6555e8 | ||
![]() |
c62f6ef139 | ||
![]() |
0c34625782 | ||
![]() |
e86fa6f9fa | ||
![]() |
e2e975f357 | ||
![]() |
8eaf6d3f88 | ||
![]() |
1cb036941e | ||
![]() |
a53ff37e89 | ||
![]() |
dc3ee35c8b | ||
![]() |
e05fe94650 | ||
![]() |
b0cb0b06a2 | ||
![]() |
a9fed1c203 | ||
![]() |
6d8407676d | ||
![]() |
1157716c9c | ||
![]() |
861615b7a4 | ||
![]() |
e12d11264e | ||
![]() |
6d7b4810da | ||
![]() |
585b747612 | ||
![]() |
55887e1a89 | ||
![]() |
39a7a1298a | ||
![]() |
90059c5ae6 | ||
![]() |
ad63179484 | ||
![]() |
6eaba9d3a8 | ||
![]() |
11ce68902c | ||
![]() |
70ca059e0f | ||
![]() |
6588f40007 | ||
![]() |
67aaba0656 | ||
![]() |
2d8eac4f12 | ||
![]() |
00d822ad4b | ||
![]() |
5cf15b26b9 | ||
![]() |
07f08a8851 | ||
![]() |
d2e0ae6394 | ||
![]() |
cbae7c38cf | ||
![]() |
15656109bc | ||
![]() |
93e415120a | ||
![]() |
c47e7c03f9 | ||
![]() |
125512d2c3 | ||
![]() |
65b0d31762 | ||
![]() |
9103529b64 | ||
![]() |
349952cb52 | ||
![]() |
305cf6b050 | ||
![]() |
a51bfb102f | ||
![]() |
78ff49a6d4 | ||
![]() |
9cd8996aeb | ||
![]() |
25232a4146 | ||
![]() |
5266d2f985 | ||
![]() |
ae35d5873a | ||
![]() |
fe9aaa0d29 | ||
![]() |
076bde7669 | ||
![]() |
592bee1ac1 | ||
![]() |
c9a7d56da4 | ||
![]() |
2157cc8e4a | ||
![]() |
2cd11ba50e | ||
![]() |
eddcedd00b | ||
![]() |
0d4fe11cfb | ||
![]() |
ecbf4e511d | ||
![]() |
16bbae832e | ||
![]() |
571da788d0 | ||
![]() |
f2eed5ece0 | ||
![]() |
2c5516310f | ||
![]() |
5550dffab8 | ||
![]() |
b9744dd6b5 | ||
![]() |
82fedb3526 | ||
![]() |
cc4f0a3dec | ||
![]() |
cdfa48d942 | ||
![]() |
c4edc871b3 | ||
![]() |
ed6d1d2aaf | ||
![]() |
b0f56dbb2c | ||
![]() |
c794efe899 | ||
![]() |
c2fb4f103d | ||
![]() |
18b192bc42 | ||
![]() |
522fc9816d | ||
![]() |
3be4fb8460 | ||
![]() |
7ca07814bb | ||
![]() |
aaea8ed956 | ||
![]() |
822ccdc349 | ||
![]() |
dbc1b9553a | ||
![]() |
04c5f08f5f | ||
![]() |
5c10385246 | ||
![]() |
90f9b6f414 | ||
![]() |
b555194d8e | ||
![]() |
0e7c4eced7 | ||
![]() |
816a40d0ec | ||
![]() |
75a819b7b4 | ||
![]() |
cf0e5be55c | ||
![]() |
492fb404fe | ||
![]() |
6453d215eb | ||
![]() |
35f13eff53 | ||
![]() |
fe2c5185eb | ||
![]() |
f1429c0d0d | ||
![]() |
d6e30b805d | ||
![]() |
e8aeaf71d4 | ||
![]() |
1500d635e9 | ||
![]() |
27798ea073 | ||
![]() |
891918269e | ||
![]() |
0cf8ec67da | ||
![]() |
217a3d107d | ||
![]() |
38a229cb78 | ||
![]() |
0598d49ba0 | ||
![]() |
f8f2dbc7f4 | ||
![]() |
7449d5dbb1 | ||
![]() |
a890b0f66e | ||
![]() |
1647ed455e | ||
![]() |
a383847d5b | ||
![]() |
3fb2a824cf | ||
![]() |
dd7579ce7c | ||
![]() |
49c51ceea4 | ||
![]() |
a5370793cb | ||
![]() |
6f72020607 | ||
![]() |
6a7c61ea3c | ||
![]() |
46c1854481 | ||
![]() |
72030a4749 | ||
![]() |
1c18884527 | ||
![]() |
1c5c695f4f | ||
![]() |
a1cef1ccda | ||
![]() |
4dfcbbf62a | ||
![]() |
d0e1a15673 | ||
![]() |
8fc9324be5 | ||
![]() |
4f4724804e | ||
![]() |
533d92509f |
45
.github/workflows/build.yml
vendored
@@ -78,7 +78,7 @@ jobs:
|
||||
|
||||
# - name: Build keepass2android (net)
|
||||
# run: |
|
||||
# make msbuild Flavor=Net
|
||||
# make dotnetbuild Flavor=Net
|
||||
|
||||
# - name: Build APK (net)
|
||||
# run: |
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
# - name: Build keepass2android (nonet)
|
||||
# run: |
|
||||
# make msbuild Flavor=NoNet
|
||||
# make dotnetbuild Flavor=NoNet
|
||||
|
||||
# - name: Build APK (nonet)
|
||||
# run: |
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
|
||||
# - name: Build keepass2android (net)
|
||||
# run: |
|
||||
# make msbuild Flavor=Net
|
||||
# make dotnetbuild Flavor=Net
|
||||
|
||||
# - name: Build APK (net)
|
||||
# run: |
|
||||
@@ -230,7 +230,7 @@ jobs:
|
||||
|
||||
# - name: Build keepass2android (nonet)
|
||||
# run: |
|
||||
# make msbuild Flavor=NoNet
|
||||
# make dotnetbuild Flavor=NoNet
|
||||
|
||||
# - name: Build APK (nonet)
|
||||
# run: |
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
- name: Add dotnetbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
# If we want to also have nmake, use this instead
|
||||
#uses: ilammy/msvc-dev-cmd@v1
|
||||
@@ -309,30 +309,49 @@ jobs:
|
||||
run: |
|
||||
make java
|
||||
|
||||
- name: Update dotnet workloads
|
||||
run: |
|
||||
dotnet workload update
|
||||
|
||||
- name: Select the manifest
|
||||
run: |
|
||||
make manifestlink Flavor=Net
|
||||
|
||||
- name: Install NuGet dependencies (net)
|
||||
run: make nuget Flavor=Net
|
||||
|
||||
- name: Build keepass2android (net)
|
||||
run: |
|
||||
make msbuild Flavor=Net
|
||||
make dotnetbuild Flavor=Net
|
||||
|
||||
- name: Build APK (net)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
env:
|
||||
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
|
||||
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
|
||||
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
|
||||
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
|
||||
run: |
|
||||
make apk Flavor=Net
|
||||
make apk Configuration=Release Flavor=Net
|
||||
|
||||
- name: Archive production artifacts (net)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed APK ('net' built on ${{ github.job }})
|
||||
name: archive APK ('net' built on ${{ github.job }})
|
||||
path: |
|
||||
src/keepass2android/bin/*/*-Signed.apk
|
||||
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
|
||||
|
||||
- name: Select the manifest
|
||||
run: |
|
||||
make manifestlink Flavor=NoNet
|
||||
|
||||
- name: Install NuGet dependencies (nonet)
|
||||
run: make nuget Flavor=NoNet
|
||||
|
||||
- name: Build keepass2android (nonet)
|
||||
run: |
|
||||
make msbuild Flavor=NoNet
|
||||
make dotnetbuild Flavor=NoNet
|
||||
|
||||
- name: Test Autofill
|
||||
working-directory: ./src/Kp2aAutofillParser.Tests
|
||||
run: dotnet test
|
||||
@@ -344,9 +363,7 @@ jobs:
|
||||
- name: Archive production artifacts (nonet)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed APK ('nonet' built on ${{ github.job }})
|
||||
name: archive APK ('nonet' built on ${{ github.job }})
|
||||
path: |
|
||||
src/keepass2android/bin/*/*-Signed.apk
|
||||
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
|
||||
|
||||
- name: Perform "make distclean"
|
||||
run: make distclean
|
||||
|
162
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
name: Create keepass2android release
|
||||
env:
|
||||
NAME: 'Release'
|
||||
|
||||
on:
|
||||
# the workflow is always triggered manually. This allows to test the apks
|
||||
# before publishing the release and not having a broken tag in the repo if that test fails.
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
|
||||
build-release:
|
||||
|
||||
runs-on: windows-2022
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
flavor: [Net, NoNet]
|
||||
target: [apk, apk_split]
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Extract key store
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||
KeyStore: "${{ github.workspace }}/kp2a.keystore"
|
||||
|
||||
shell: bash
|
||||
run: |
|
||||
echo $KeyStore
|
||||
echo $KEYSTORE_BASE64 | base64 --decode > $KeyStore
|
||||
|
||||
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('src/**/*.csproj', 'src/**/packages.config') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
# Workaround an issue when building on windows-2022. Error was
|
||||
# D8 : OpenJDK 64-Bit Server VM warning : INFO: os::commit_memory(0x00000000ae400000, 330301440, 0) failed; error='The paging file is too small for this operation to complete' (DOS error/errno=1455) [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj]
|
||||
# C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.D8.targets(81,5): error MSB6006: "java.exe" exited with code 1. [D:\a\keepass2android\keepass2android\src\keepass2android\keepass2android-app.csproj]
|
||||
- name: Configure Pagefile
|
||||
uses: al-cheb/configure-pagefile-action@a3b6ebd6b634da88790d9c58d4b37a7f4a7b8708 # v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
|
||||
- name: Add msbuild/dotnet to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
# If we want to also have nmake, use this instead
|
||||
#uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Switch to JDK-17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Display java version
|
||||
run: java -version
|
||||
|
||||
- name: Build native dependencies
|
||||
shell: cmd
|
||||
run: |
|
||||
make native
|
||||
|
||||
- name: Build java dependencies
|
||||
shell: cmd
|
||||
run: |
|
||||
make java
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
- name: Update dotnet workloads
|
||||
run: |
|
||||
dotnet workload update
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
|
||||
- name: Select the manifest
|
||||
run: |
|
||||
make manifestlink Flavor=${{ matrix.flavor }}
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
|
||||
- name: Install NuGet dependencies
|
||||
run: make nuget Flavor=${{ matrix.flavor }}
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
- name: Build APK (net)
|
||||
env:
|
||||
KeyStore: "${{ github.workspace }}/kp2a.keystore"
|
||||
MyAndroidSigningStorePass: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
MyAndroidSigningKeyPass: ${{ secrets.KEY_PASSWORD }}
|
||||
DropboxAppKey: ${{ secrets.DROPBOX_APP_KEY }}
|
||||
DropboxAppSecret: ${{ secrets.DROPBOX_APP_SECRET }}
|
||||
DropboxAppFolderAppKey: ${{ secrets.DROPBOX_APP_FOLDER_APP_KEY }}
|
||||
DropboxAppFolderAppSecret: ${{ secrets.DROPBOX_APP_FOLDER_APP_SECRET }}
|
||||
|
||||
run: |
|
||||
make ${{ matrix.target }} Configuration=Release Flavor=${{ matrix.flavor }}
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: keepass2android_${{ matrix.target }}_${{ matrix.flavor }}
|
||||
# the first line is for "apk" target, the second line is for "apk_split" target
|
||||
path: |
|
||||
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
|
||||
src/keepass2android-app/bin/Release/net9.0-android/*/publish/*.apk
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
- name: Rename apks
|
||||
# after updating to .net9, the naming scheme of the output apks has changed. rename them to the old scheme
|
||||
# for consistancy with previous releases
|
||||
run: |
|
||||
for apk in src/keepass2android-app/bin/Release/net9.0-android/android-*/publish/*-Signed.apk; do
|
||||
[ -e "$apk" ] || continue # if glob above doesn't return anything, the loop is still executed once
|
||||
arch=$(basename "$(dirname "$(dirname "$apk")")") # e.g. "android-arm64"
|
||||
base=$(basename "$apk" .apk) # e.g. "keepass2android.keepass2android_nonet-Signed"
|
||||
mv "$apk" "$(dirname "$apk")/${base}-${arch#android-}.apk"
|
||||
done
|
||||
shell: bash
|
||||
|
||||
- name: List apks
|
||||
run: find . -type f -name "*.apk"
|
||||
shell: bash
|
||||
|
||||
- name: Upload APK to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
src/keepass2android-app/bin/Release/net9.0-android/publish/*.apk
|
||||
src/keepass2android-app/bin/Release/net9.0-android/*/publish/*.apk
|
2
.gitignore
vendored
@@ -64,7 +64,7 @@ Thumbs.db
|
||||
/src/java/android-filechooser/code/projectzip/project.zip
|
||||
/src/java/android-filechooser/code/unused.txt
|
||||
|
||||
/src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
|
||||
/src/Kp2aBusinessLogic/Io/DropboxFileStorage.g.cs
|
||||
|
||||
/src/java/workspace/DriveTest
|
||||
|
||||
|
90
Makefile
@@ -4,10 +4,10 @@
|
||||
# This Makefile can be used on both unix-like (use make) & windows (with GNU make)
|
||||
#
|
||||
# append the Configuration variable to 'make' call with value to use in '/p:Configuration='
|
||||
# of msbuild command.
|
||||
# of dotnetbuild command.
|
||||
#
|
||||
# append the Flavor variable to 'make' call with value to use in '/p:Flavor='
|
||||
# of msbuild command.
|
||||
# of dotnetbuild command.
|
||||
#
|
||||
# Example:
|
||||
# make Configuration=Release Flavor=NoNet
|
||||
@@ -18,15 +18,16 @@
|
||||
# - native: build the native libs
|
||||
# - java: build the java libs
|
||||
# - nuget: restore NuGet packages
|
||||
# - msbuild: build the project
|
||||
# - dotnetbuild: build the project
|
||||
# - apk: same as all
|
||||
# - manifestlink: creates a symlink (to be used in building) to the AndroidManifest corresponding to the selected Flavor
|
||||
#
|
||||
# - distclean: run a 'git clean -xdff'. Remove everyhing that is not in the git tree.
|
||||
# - clean: all clean_* targets below
|
||||
# - clean_native: clean native lib
|
||||
# - clean_java: call clean target of java libs
|
||||
# - clean_nuget: cleanup the 'nuget restore'
|
||||
# - clean_msbuild: call clean target of msbuild
|
||||
# - clean_dotnet: call clean target of dotnetbuild
|
||||
#
|
||||
#
|
||||
#
|
||||
@@ -59,45 +60,23 @@ $(info MAKESHELL: $(MAKESHELL))
|
||||
$(info SHELL: $(SHELL))
|
||||
$(info )
|
||||
|
||||
# On linux use xabuild, on Windows use MSBuild.exe, otherwise (macos?) use msbuild.
|
||||
ifeq ($(detected_OS),Linux)
|
||||
MSBUILD_binary := xabuild
|
||||
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary))
|
||||
DOTNET_binary := dotnet
|
||||
DOTNET := $(shell $(WHICH) $(DOTNET_binary))
|
||||
else ifeq ($(detected_OS),Windows)
|
||||
MSBUILD_binary := MSBuild.exe
|
||||
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary) 2> nul)
|
||||
ifeq ($(MSBUILD),)
|
||||
# Additional heuristic to find MSBUILD_BINARY on Windows
|
||||
VSWHERE := "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
VSWHERE_CHECK := $(shell @echo off & $(VSWHERE) 2> nul || echo VSWHERE_NOT_FOUND)
|
||||
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
|
||||
MSBUILD := $(shell @echo off & $(VSWHERE) -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe)
|
||||
VS_INSTALL_PATH := $(shell @echo off & $(VSWHERE) -property installationPath)
|
||||
endif
|
||||
endif
|
||||
DOTNET_binary := dotnet
|
||||
DOTNET := $(shell $(WHICH) $(DOTNET_binary) 2> nul)
|
||||
else
|
||||
MSBUILD_binary := msbuild
|
||||
MSBUILD := $(shell $(WHICH) $(MSBUILD_binary))
|
||||
DOTNET_binary := dotnet
|
||||
DOTNET := $(shell $(WHICH) $(DOTNET_binary))
|
||||
endif
|
||||
|
||||
ifeq ($(MSBUILD),)
|
||||
ifeq ($(DOTNET),)
|
||||
$(info )
|
||||
$(info '$(MSBUILD_binary)' binary could not be found. Check it is in your PATH.)
|
||||
ifeq ($(detected_OS),Windows)
|
||||
ifneq ($(VSWHERE_CHECK),VSWHERE_NOT_FOUND)
|
||||
$(info )
|
||||
$(info You may retry after running in the command prompt:)
|
||||
$(info )
|
||||
$(info "$(VS_INSTALL_PATH)\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64)
|
||||
$(info )
|
||||
$(info If this doesn't work, install/find the location of vcvarsall.bat)
|
||||
$(info or install and add msbuild.exe to your PATH)
|
||||
$(info )
|
||||
endif
|
||||
endif
|
||||
$(info '$(DOTNET_binary)' binary could not be found. Check it is in your PATH.)
|
||||
$(error )
|
||||
endif
|
||||
$(info MSBUILD: $(MSBUILD))
|
||||
$(info DOTNET: $(DOTNET))
|
||||
$(info )
|
||||
|
||||
ifeq ($(ANDROID_SDK_ROOT),)
|
||||
@@ -116,7 +95,7 @@ endif
|
||||
$(info ANDROID_NDK_ROOT: $(ANDROID_NDK_ROOT))
|
||||
|
||||
ifneq ($(Configuration),)
|
||||
MSBUILD_PARAM = -p:Configuration="$(Configuration)"
|
||||
DOTNET_PARAM = -p:Configuration="$(Configuration)"
|
||||
else
|
||||
$(warning Configuration environment variable not set.)
|
||||
endif
|
||||
@@ -126,7 +105,7 @@ CREATE_MANIFEST_LINK :=
|
||||
|
||||
MANIFEST_FILE :=
|
||||
ifneq ($(Flavor),)
|
||||
MSBUILD_PARAM += -p:Flavor="$(Flavor)"
|
||||
DOTNET_PARAM += -p:Flavor="$(Flavor)"
|
||||
ifneq ($(Flavor),)
|
||||
ifeq ($(Flavor),Debug)
|
||||
MANIFEST_FILE := AndroidManifest_debug.xml
|
||||
@@ -151,7 +130,7 @@ else
|
||||
endif
|
||||
|
||||
ifneq ($(KeyStore),)
|
||||
MSBUILD_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
|
||||
DOTNET_PARAM += -p:AndroidKeyStore=True -p:AndroidSigningKeyStore="$(KeyStore)" -p:AndroidSigningStorePass=env:MyAndroidSigningStorePass -p:AndroidSigningKeyPass=env:MyAndroidSigningKeyPass -p:AndroidSigningKeyAlias="kp2a"
|
||||
endif
|
||||
|
||||
ifeq ($(detected_OS),Windows)
|
||||
@@ -175,7 +154,7 @@ endif
|
||||
# Recursive wildcard: https://stackoverflow.com/a/18258352
|
||||
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
|
||||
|
||||
$(info MSBUILD_PARAM: $(MSBUILD_PARAM))
|
||||
$(info DOTNET_PARAM: $(DOTNET_PARAM))
|
||||
$(info nuget path: $(shell $(WHICH) nuget))
|
||||
$(info )
|
||||
|
||||
@@ -253,7 +232,7 @@ OUTPUT_PluginQR = src/java/Keepass2AndroidPluginSDK2/app/build/outputs/aar/Keepa
|
||||
.PHONY: native $(NATIVE_COMPONENTS) clean_native $(NATIVE_CLEAN_TARGETS) \
|
||||
java $(JAVA_COMPONENTS) clean_java $(JAVA_CLEAN_TARGETS) \
|
||||
nuget clean_nuget \
|
||||
msbuild clean_msbuild \
|
||||
dotnetbuild clean_dotnet \
|
||||
apk all clean
|
||||
|
||||
all: apk
|
||||
@@ -302,7 +281,7 @@ ifeq ($(shell $(WHICH) nuget),)
|
||||
endif
|
||||
$(RMFILE) stamp.nuget_*
|
||||
nuget restore src/KeePass.sln
|
||||
$(MSBUILD) src/KeePass.sln -t:restore $(MSBUILD_PARAM) -p:RestorePackagesConfig=true
|
||||
$(DOTNET) restore src/KeePass.sln $(DOTNET_PARAM) -p:RestorePackagesConfig=true
|
||||
@echo "" > stamp.nuget_$(Flavor)
|
||||
|
||||
manifestlink:
|
||||
@@ -311,20 +290,21 @@ manifestlink:
|
||||
$(CREATE_MANIFEST_LINK)
|
||||
|
||||
#####
|
||||
src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs:
|
||||
ifeq ($(detected_OS),Windows)
|
||||
$(CP) src\Kp2aBusinessLogic\Io\DropboxFileStorageKeysDummy.cs src\Kp2aBusinessLogic\Io\DropboxFileStorageKeys.cs
|
||||
else
|
||||
$(CP) src/Kp2aBusinessLogic/Io/DropboxFileStorageKeysDummy.cs $@
|
||||
endif
|
||||
|
||||
msbuild: manifestlink native java nuget src/Kp2aBusinessLogic/Io/DropboxFileStorageKeys.cs
|
||||
$(MSBUILD) src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(MSBUILD_PARAM) -p:Platform="Any CPU" -m
|
||||
dotnetbuild: manifestlink native java nuget
|
||||
$(DOTNET) build src/KeePass.sln -target:keepass2android-app -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -p:BuildProjectReferences=true $(DOTNET_PARAM) -p:Platform="Any CPU" -m
|
||||
|
||||
apk: msbuild
|
||||
$(MSBUILD) src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(MSBUILD_PARAM) -p:Platform=AnyCPU -m
|
||||
apk: manifestlink native java nuget
|
||||
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m
|
||||
|
||||
build_all: msbuild
|
||||
apk_split: manifestlink native java nuget
|
||||
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm
|
||||
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-arm64
|
||||
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x86
|
||||
$(DOTNET) publish src/keepass2android-app/keepass2android-app.csproj -p:AndroidSdkDirectory="$(ANDROID_SDK_ROOT)" -t:SignAndroidPackage $(DOTNET_PARAM) -p:Platform=AnyCPU -m -p:RuntimeIdentifier=android-x64
|
||||
src/build-scripts/rename-output-apks.sh src/keepass2android-app/bin/Release/net8.0-android/
|
||||
|
||||
build_all: dotnetbuild
|
||||
|
||||
##### Cleanup targets
|
||||
|
||||
@@ -368,10 +348,10 @@ else
|
||||
endif
|
||||
$(RMFILE) stamp.nuget_*
|
||||
|
||||
clean_msbuild:
|
||||
$(MSBUILD) src/KeePass.sln -target:clean $(MSBUILD_PARAM)
|
||||
clean_dotnet:
|
||||
$(DOTNET) clean src/KeePass.sln $(DOTNET_PARAM)
|
||||
|
||||
clean: clean_native clean_java clean_nuget clean_msbuild
|
||||
clean: clean_native clean_java clean_nuget clean_dotnet
|
||||
|
||||
distclean: clean
|
||||
ifneq ("$(wildcard ./allow_git_clean)","")
|
||||
|
@@ -1,7 +1,7 @@
|
||||
files:
|
||||
- source: src/keepass2android/Resources/values/strings.xml
|
||||
- source: src/keepass2android-app/Resources/values/strings.xml
|
||||
translation: >-
|
||||
/src/keepass2android/Resources/values-%two_letters_code%/%original_file_name%
|
||||
/src/keepass2android-app/Resources/values-%two_letters_code%/%original_file_name%
|
||||
translate_attributes: '0'
|
||||
content_segmentation: '0'
|
||||
languages_mapping:
|
||||
|
@@ -1,5 +0,0 @@
|
||||
As of December 2017, Google does not accept the use of Accessibility services for anything except helping people with disabilities. This means that Keepass2Android can no longer provide the accessibility service based AutoFill feature. Otherwise, Google would remove Keepass2Android from Play Store.
|
||||
|
||||
If you want to continue using this feature, please [install the Accessibility service based AutoFill plugin](https://github.com/PhilippC/kp2a_accservice_autofill/releases/).
|
||||
|
||||
After installation, please enable the accessibility service "KP2A AutoFillPlugin" in the Android system settings. When trying to use the plugin for the first time, KP2A will ask you if the plugin may access the Keepass database. Please accept this to use the plugin.
|
@@ -1,18 +0,0 @@
|
||||
# Installing dictionaries
|
||||
Keepass2Android will load dictionaries for your current language both from AnySoftKeyboard and from Hacker's keyboard.
|
||||
* For AnySoftKeyboard dictionaries visit: [https://play.google.com/store/search?q=anysoftkeyboard+dictionary&c=apps](https://play.google.com/store/search?q=anysoftkeyboard+dictionary&c=apps)
|
||||
* For Hacker's Keyboard dictionaries visit: [https://play.google.com/store/search?q=hacker%27s%20keyboard%20dictionary&c=apps](https://play.google.com/store/search?q=hacker%27s%20keyboard%20dictionary&c=apps)
|
||||
|
||||
# Automatic keyboard switching (requires ADB)
|
||||
Starting with Keepass2Android 1.02-pre1, you can use the [KeyboardSwap Plugin](https://play.google.com/store/apps/details?id=keepass2android.plugin.keyboardswap2) to switch to the KP2A keyboard automatically instead of bringing up the Input method selection dialog (e.g. after using the Share URL feature). To setup the plugin please follow the instructions on [the PlayStore website](https://play.google.com/store/apps/details?id=keepass2android.plugin.keyboardswap2)
|
||||
|
||||
# Deprecated: Automatic keyboard switching on rooted devices
|
||||
|
||||
In order to automatically switch to the KP2A keyboard and back, you need to
|
||||
* have a rooted device (per Android security policies)
|
||||
* have at least KP2A version 0.9.3-pre2
|
||||
* install the Secure Settings app with the "System+" module available in Secure Settings v. 1.3.4 and above. **Note:** This is no longer available for recent Android versions.
|
||||
* Go to the KP2A keyboard settings. Enable auto-switch on rooted devices.
|
||||
|
||||
If you go to a website, select "Share URL" -> Keepass2Android, the keyboard should be switched as soon as you locate the entry or when it's found automatically.
|
||||
|
@@ -1,239 +0,0 @@
|
||||
# How to build Keepass2Android
|
||||
|
||||
## Overview
|
||||
|
||||
Keepass2Android is a Mono for Android app. This means that you need Xamarin's Mono for Android to build it. However, it also uses several components written in Java, so there are also Android-Studio projects involved. To make things even worse, parts of the keyboard and kdb-library are written in native code.
|
||||
|
||||
To build KP2A from scratch, you need:
|
||||
- Xamarin's Mono for Android (also included in Visual Studio)
|
||||
- Android SDK & NDK
|
||||
|
||||
Prior to building Keepass2Android, you need to build some of its components (from command line). Then you can build the full project either through Visual Studio, or through command line.
|
||||
|
||||
By using the command line, you can build on Windows, macOS or Linux.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Common to all architectures
|
||||
- Install Android SDK & NDK (either manually with Google's [sdkmanager](https://developer.android.com/studio/command-line/sdkmanager), or through Android Studio). Visual Studio also installs a version of it, but in the end the directory must be writable and in a path without spaces (see below) so as to be able to build the components.
|
||||
- Fetch the main repository of Keepass2Android and all submodules
|
||||
- Note that VisualStudio can do this for you, otherwise run:
|
||||
- `git submodule init && git submodule update`
|
||||
|
||||
### On Windows or macOS
|
||||
- Install Visual Studio (for example 2019) with Xamarin.Android (ie. with capability to build Android apps). This should provide the needed tools like
|
||||
- Xamarin.Android
|
||||
- MSBuild
|
||||
- Java JDK
|
||||
- If you plan to build also from the command line:
|
||||
- Install the MSVC build tools of visual studio. They provide the `vcvarsall.bat` file which among other things adds MSBuild to the PATH.
|
||||
- Install [NuGet](https://www.nuget.org/downloads) to build also with "make". Alternatively, on Windows, if you use [chocolatey](https://chocolatey.org), run as administrator:
|
||||
- `choco install nuget.commandline`
|
||||
- Check that you have access to 'GNU make'.
|
||||
- On Windows, it is usually not available by default. But the Android NDK provides it. You can find it in `%ANDROID_NDK_ROOT%\prebuilt\windows-x86_64\bin\make.exe`. Alternatively, on Windows, if you use [chocolatey](https://chocolatey.org), run as administrator:
|
||||
- `choco install make`
|
||||
- On macOS, it is usually only installed if you have developer command line tools installed or if you use [homebrew](https://brew.sh) or [macports](https://www.macports.org/). As an alternative it may be available in the Android NDK at `%ANDROID_NDK_ROOT%/prebuilt/darwin-x86_64/bin/make`.
|
||||
|
||||
### On Linux
|
||||
- Install Java's JDK
|
||||
- On Debian, for example: `apt install default-jdk-headless`.
|
||||
|
||||
- Install [Mono](https://www.mono-project.com/)
|
||||
- This should provide `msbuild` & `xabuild` binary
|
||||
- On Debian, after having added the repo from above, install with `apt install -t <repo_name> mono-devel msbuild`. A value for `<repo_name>` could be `stable-buster` for example, depending on which one you chose. You could also install the `mono-complete` package if you prefer.
|
||||
|
||||
- Install Xamarin.Android
|
||||
- ~~Option 1: Use the mono-project [CI builds](https://dev.azure.com/xamarin/public/_build/latest?definitionId=48&branchName=main&stageName=Linux)~~ **NOTE:** KP2A now requires Xamarin.Android v13, which is newer than the current CI build; until a more recent CI build is available, this option is unfortunately no longer viable.
|
||||
- Option 2: [Build it from source](https://github.com/xamarin/xamarin-android/blob/master/Documentation/README.md#building-from-source)
|
||||
|
||||
- Install NuGet package of your distribution
|
||||
- On Debian/Ubuntu: `apt install nuget`
|
||||
|
||||
- Install [libzip](https://libzip.org/) for your distribution for some Xamarin.Android versions
|
||||
- This may not be relevant anymore: for example, with Xamarin.Android 11.4.99. this is not needed.
|
||||
- Some versions of Xamarin may require `libzip4`. If you are in this case:
|
||||
- On Debian/Ubuntu, install it with `apt install libzip4`.
|
||||
- Other distributions ship only `libzip5`. As a dirty workaround, it's possible to symlink `libzip.so.5` to `libzip.so.4`. Luckily, it appears to be working. For example:
|
||||
- `sudo ln -s /usr/lib/libzip.so.5 /usr/lib/libzip.so.4`
|
||||
- or `sudo ln -s /usr/lib64/libzip.so.5 /usr/lib/libzip.so.4`
|
||||
|
||||
## Building the required components:
|
||||
|
||||
This is done on the command line and requires the Android SDK & NDK and Java JDK.
|
||||
|
||||
### On Windows
|
||||
- Setup your environment:
|
||||
- Set these environment variables for Android's SDK & NDK
|
||||
- `ANDROID_HOME` (for example `set ANDROID_HOME=C:\PATH\TO\android-sdk`)
|
||||
- `ANDROID_SDK_ROOT` (for example `set ANDROID_SDK_ROOT=C:\PATH\TO\android-sdk`)
|
||||
- `ANDROID_NDK_ROOT` (for example `set ANDROID_NDK_ROOT=C:\PATH\TO\android-sdk\ndk\version`)
|
||||
|
||||
**Note:** Care must be taken when setting the above variables to **not** include a trailing backslash in the path. A trailing backslash may cause `make` to fail.
|
||||
|
||||
**Note**: If the path to the Android SDK contains spaces, you **must** do one of these:
|
||||
- either put the Android SDK into a path without spaces.
|
||||
- or create a symlink to that path which doesn't contain spaces. Attention: this requires **administrator** priveleges. For example:
|
||||
|
||||
```
|
||||
IF NOT EXIST C:\Android ( MKDIR C:\Android ) &&
|
||||
MKLINK /D C:\Android\android-sdk "C:\Program Files (x86)\Android\android-sdk"
|
||||
```
|
||||
This is because [Android NDK doesn't support being installed in a path with spaces](https://github.com/android/ndk/issues/1400).
|
||||
|
||||
**Note**: The Android SDK path will require to be writeable because during the build, some missing components might be downloaded & installed.
|
||||
|
||||
- If you have "GNU make" available on your windows system, you may build by using the Makefile. You can also find a `make` executable in `%ANDROID_NDK_ROOT%\prebuilt\windows-x86_64\bin\make.exe`. To use it, see the instructions for Linux/macOS. Basically, just run `make` or `mingw32-make` depending on which distribution of GNU make for windows you have installed.
|
||||
|
||||
- Otherwise proceed as below:
|
||||
|
||||
1. Build argon2
|
||||
|
||||
```
|
||||
cd src/java/argon2
|
||||
%ANDROID_NDK_ROOT%/ndk-build.cmd
|
||||
```
|
||||
1. Build the other java components
|
||||
|
||||
```
|
||||
cd src/build-scripts
|
||||
build-java.bat
|
||||
```
|
||||
|
||||
`build-java.bat` will call `gradlew` for several Java modules.
|
||||
|
||||
**Notes:**
|
||||
|
||||
- For building the java parts, it is suggested to keep a short name (e.g. "c:\projects\keepass2android") for the root project directory. Otherwise the Windows path length limit might be hit when building.
|
||||
- Before building the java parts, make sure you have set the ANDROID_HOME variable or create a local.properties file inside the directories with a gradlew file. It is recommended to use the same SDK location as that of the Xamarin build.
|
||||
- On some environments, `make` can fail to properly use the detected `MSBUILD` tools. This seems to be due to long pathnames and/or spaces in pathnames. It may be required to explicitly set the `MSBUILD` path using 8.3 "short" path notation:
|
||||
- Determine the location of `MSBUILD` (e.g. `C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe`)
|
||||
- [Generate the "short" path](https://superuser.com/a/728792) of that location (e.g.: `C:\PROGRA~1\MICROS~2\2022\COMMUN~1\MSBuild\Current\Bin\MSBuild.exe`)
|
||||
- When running `make` specify the location of ``MSBUILD` explicitly (e.g.: `make MSBUILD="C:\PROGRA~1\MICROS~2\2022\COMMUN~1\MSBuild\Current\Bin\MSBuild.exe`
|
||||
|
||||
|
||||
### On Linux/macOS
|
||||
|
||||
- Setup your environment:
|
||||
- Set these environment variables for Android's SDK & NDK
|
||||
- `ANDROID_HOME` (for example `export ANDROID_HOME=/path/to/android-sdk/`)
|
||||
- `ANDROID_SDK_ROOT` (for example `export ANDROID_SDK_ROOT=/path/to/android-sdk/`)
|
||||
- `ANDROID_NDK_ROOT` (for example `export ANDROID_NDK_ROOT=/path/to/android-sdk/ndk/version`)
|
||||
|
||||
- Update your PATH environment variable so that it can access `nuget`, `msbuild` or `xabuild` (for linux):
|
||||
- On Linux:
|
||||
- add `xabuild` to your path: `export PATH=/path/to/xamarin.android-oss/bin/Release/bin/:$PATH`
|
||||
- On macOS:
|
||||
- you may similarly need to add `msbuild` & `nuget` to your PATH.
|
||||
|
||||
- Start the build:
|
||||
- This will use the Makefile at the root of the project (requires GNU make). To build everything (components & Keepass2Android APK) in a single command simply run:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
- Otherwise, if you prefer to do step by step
|
||||
|
||||
1. Build argon2
|
||||
|
||||
```
|
||||
make native
|
||||
```
|
||||
|
||||
1. Build the other java components
|
||||
|
||||
```
|
||||
make java
|
||||
```
|
||||
|
||||
## Building Keepass2Android:
|
||||
|
||||
These are the basic steps to build Keepass2Android. You can also build Keepass2Android Offline. For this, configure the build by using the [Flavors](#Flavors).
|
||||
|
||||
### With Visual Studio
|
||||
|
||||
- On windows or on macOS open the src/KeePass.sln file with visual studio, and choose to build the project named 'keepass2android-app'
|
||||
|
||||
### Command Line
|
||||
|
||||
#### Windows, Macos & Linux
|
||||
to build the APK, simply run:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
or to skip building the APK:
|
||||
|
||||
```
|
||||
make msbuild
|
||||
```
|
||||
|
||||
## Where is the APK ?
|
||||
The Apk can be installed on a device.
|
||||
It is located in `src/keepass2android/bin/*/*-Signed.apk`
|
||||
|
||||
If you build with Visual Studio, the APK is not produced automatically. You need to perform some extra step. See the documentation of Visual Studio on how to proceed.
|
||||
|
||||
## Flavors
|
||||
|
||||
Keepass2Android is distributed in two flavors.
|
||||
- Keepass2Android (aka `net`)
|
||||
- Keepass2Android Offline (aka `nonet`)
|
||||
|
||||
The flavor is set through a MSBuild Property named "`Flavor`". The possible values are '`Net`' and '`NoNet`'.
|
||||
|
||||
The value of the Flavor property is used in 2 projects:
|
||||
- `keepass2android-app` (in `src/keepass2android`)
|
||||
- `Kp2aBusinessLogic` (in `src/keepass2android`)
|
||||
|
||||
Its value is set inside the `*.csproj` file (XML format) of each project in the `Project`/`PropertyGroup`/`Flavor` node.
|
||||
By default its value is set to an empty string so that development is made with `AndroidManifest_debug.xml` on the '`net`' flavor.
|
||||
|
||||
This is the behaviour of the build system depending on the value of Flavor:
|
||||
| Flavor | What is built | `AndroidManifest.xml` used |
|
||||
| ----- | ----- | ----- |
|
||||
| `` (empty string): This is the default value. | Keepass2Android | `AndroidManifest_debug.xml` |
|
||||
| `Net` | Keepass2Android | `AndroidManifest_net.xml` |
|
||||
| `NoNet` | Keepass2Android Offline | `AndroidManifest_nonet.xml` |
|
||||
|
||||
### Select/Change flavor:
|
||||
|
||||
When building, by default, the flavor is not set. So the value used is the value of the Flavor property in *.csproj file. This should result on doing a build of the 'net' flavor.
|
||||
|
||||
You can force the Flavor by setting the Flavor property.
|
||||
|
||||
Proceed this way:
|
||||
|
||||
#### Command line
|
||||
|
||||
##### Windows, Macos & Linux
|
||||
|
||||
To force building 'net' with `make`, run:
|
||||
|
||||
```
|
||||
make Flavor=Net
|
||||
```
|
||||
|
||||
To build 'nonet' with `make`, run:
|
||||
|
||||
```
|
||||
make Flavor=NoNet
|
||||
```
|
||||
|
||||
##### MSBuild
|
||||
|
||||
To build with MSBuild directly on the command line, set the flavor with `-p:Flavor=value` argument. For example:
|
||||
|
||||
```
|
||||
MSBuild src/KeePass.sln ... -p:Flavor=NoNet
|
||||
```
|
||||
|
||||
#### Visual Studio
|
||||
When building with Visual Studio, edit the `*.csproj` file (XML format) and set the value in the `Project`/`PropertyGroup`/`Flavor` node. This is needed only for the projects that use the flavors.
|
||||
|
||||
**Note:** When switching between flavors, be sure to clean the previous build before.
|
||||
|
||||
## Makefile
|
||||
|
||||
It is possible to override the project's default 'Flavor' (Net, NoNet) and 'Configuration' (Release, Debug) by passing it as argument to `make`. See the header of the Makefile to see what can be done.
|
@@ -1,118 +0,0 @@
|
||||
<div class="wikidoc">
|
||||
<h1>Comparison of Keepass apps for Android</h1>
|
||||
<p>This page was created to give you a short overview of the features of Keepass2Android vs. Keepass2Android Offline. As Keepass2Android is based on Keepassdroid (by Brian Pellin), there are quite a few similarities here, as well, so we added Keepass2Android
|
||||
vs. Keepassdroid comparison as well. "Better" properties are highlighted in bold. The page was created in 12/2013. If any information is out of date, please leave a note in the comments section.</p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>
|
||||
<h3>Keepass2Android</h3>
|
||||
</th>
|
||||
<th>
|
||||
<h3>Keepass2Android Offline</h3>
|
||||
</th>
|
||||
<th>
|
||||
<h3>Keepassdroid</h3>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Play Store link</strong></td>
|
||||
<td><a href="https://play.google.com/store/apps/details?id=keepass2android.keepass2android">https://play.google.com/store/apps/details?id=keepass2android.keepass2android</a></td>
|
||||
<td><a href="https://play.google.com/store/apps/details?id=keepass2android.keepass2android_nonet">https://play.google.com/store/apps/details?id=keepass2android.keepass2android_nonet</a></td>
|
||||
<td><a href="https://play.google.com/store/apps/details?id=com.android.keepass">https://play.google.com/store/apps/details?id=com.android.keepass</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Technology</strong></td>
|
||||
<td>Mono for Android, Java</td>
|
||||
<td>Mono for Android, Java</td>
|
||||
<td>Java</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass 2.x (kdbx) support</strong></td>
|
||||
<td><strong>write and read</strong></td>
|
||||
<td><strong>write and read</strong></td>
|
||||
<td>read (write in beta)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass 2.x feature support</strong></td>
|
||||
<td><strong>Viewing and editing of tags, attachments, additional fields, TAN support</strong></td>
|
||||
<td><strong>Viewing and editing of tags, attachments, additional fields, TAN support</strong></td>
|
||||
<td>Edit standard fields only, no display/edit of attachments</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass 1.x (kdb) support</strong></td>
|
||||
<td>currently read-only</td>
|
||||
<td>currently read-only</td>
|
||||
<td><strong>yes</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Storage locations</strong></td>
|
||||
<td>SD Card,<strong> Cloud (Dropbox, Google Drive, OneDrive), Web (FTP/HTTP/HTTPS/WebDav), SFTP</strong></td>
|
||||
<td>SD Card</td>
|
||||
<td>SD Card</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>File choosers</strong></td>
|
||||
<td><strong>Internal or third party</strong></td>
|
||||
<td><strong>Internal or third party</strong></td>
|
||||
<td>Third party</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Required permissions</strong></td>
|
||||
<td>Internet, SD card, Manage accounts</td>
|
||||
<td><strong>SD card</strong></td>
|
||||
<td><strong>SD card</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Android 4.x style</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Database locking</strong></td>
|
||||
<td><strong>QuickUnlock</strong> or full lock</td>
|
||||
<td><strong>QuickUnlock</strong> or full lock</td>
|
||||
<td>Full lock only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Browser integration</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Secure keyboard</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Database unlocking</strong></td>
|
||||
<td><strong>password, keyfile, One-Time-Passwords (supports Yubikey NEO with NFC), KeeChallenge</strong></td>
|
||||
<td><strong>password, keyfile, One-Time-Passwords (supports Yubikey NEO with NFC), KeeChallenge</strong></td>
|
||||
<td>password, keyfile</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Keepass plugin compatibility</strong></td>
|
||||
<td><strong>Twofish Cipher, KeeChallenge, TrayTOTP, KeeOTP</strong></td>
|
||||
<td><strong>Twofish Cipher, KeeChallenge, TrayTOTP, KeeOTP</strong></td>
|
||||
<td>Twofish Cipher</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Extensible with plugins</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Database export</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td><strong>yes</strong></td>
|
||||
<td>no</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p> </p>
|
||||
</div><div class="ClearBoth"></div>
|
@@ -1,125 +1 @@
|
||||
**Note:** This is an incomplete and preliminary documentation. More documentation will be added as requests come in or when the app is more feature stable.
|
||||
If you want, I'd be happy if you contribute texts for this place!
|
||||
|
||||
If you think something is missing in the documentation, please create an issue at https://github.com/PhilippC/keepass2android/issues
|
||||
|
||||
# What you should know and think about
|
||||
If you store important information using Keepass2Android, you should know a little bit about what's going on:
|
||||
* Keepass2Android stores your password in an encrypted file. It is *your responsibility* to backup this file regularly and safely.
|
||||
* There is no way for anyone, including the app's author, to access the information stored in your password database without
|
||||
* having the database file
|
||||
* knowing the master password (and additional second factor if you chose one)
|
||||
This means that **if you forget the master password, your database is lost**! So make sure you remember the password and retain any second factor method (if one is used).
|
||||
* You might also want to think about:
|
||||
* What happens if I have an accident? Should any trusted person be able to access my database?
|
||||
* What happens if my phone gets lost or stolen? Do I know how to recover my database from a backup or the cloud?
|
||||
|
||||
|
||||
# Getting started
|
||||
|
||||
## Opening an existing database
|
||||
Many users are already using Keepass 2 on Windows and thus have their passwords stored in a Keepass database, typically a file with ending .kdbx. For opening such an existing database, there are two main options:
|
||||
* You can open the file directly if it is located on a webserver or in the cloud. Use "Open Database" on the startscreen. By default, files from the cloud or servers are cached in the application's cache directory after loading them once. This allows to access your files even when you're offline.
|
||||
* If you don't have your database stored on a webserver or in the cloud (or if you're using KP2A Offline) you need to copy your kdbx-Database to your phone. I suggest to use a sync tool like FolderSync. Such a tool copies your database to your local storage, so you always have it accessible. FolderSync can access your database if you have it on a network share or use any other common storage.
|
||||
|
||||
## Creating a new database
|
||||
Select "Create new database" from the start screen. Tap the integrated help icons for more information. Note that by default, the database is created as a local file. Please consider making backups regularly or select a location in the cloud.
|
||||
|
||||
## Getting passwords into password fields
|
||||
There are many ways how to enter the passwords from your database in the corresponding fields. By default, the clipboard as well as the KP2A keyboard are activated in the settings:
|
||||
* The KP2A keyboard is the recommended way because it's safe against clipboard loggers: Whenever you select an entry, the KP2A keyboard notification will appear in the notification bar. Click it to activate the keyboard. (The first time you do this, you are required to enable the keyboard in the system settings. This must be done by the user for Android security reasons.) As soon as it's activated, you can tap a field where you want to enter data from the selected entry. The KP2A keyboard will come up. Click the KP2A key (on the bottom left) to select whether you want to enter Username/password etc. When you're done, click the Keyboard key (next to the KP2A key) to switch back to your favorite keyboard.
|
||||
* You can enable the Keepass2Android Autofill service in the system's Autofill settings (Android 8+) which allows to fill data using Android's accessibility system. This works with many apps including Firefox browser but is not supported for Chrome (when writing this).
|
||||
* The clipboard based approach can be used as well: Pull the notification bar down and select "Copy username/password to clipboard". Then long-tap the field where you want to paste the data. A small "paste" button should come up. Note, however, that information in the clipboard can be monitored by all apps on your device and clearing the clipboard is not always possible.
|
||||
|
||||
These options can be used in different workflows:
|
||||
### Browser-based workflow
|
||||
If you are browsing the web and need to enter crendentials for a webpage, a simple and powerful workflow is to use the "Share URL" option from the browser's menu. Then select Keepass2Android (or KP2A Offline). Open your database (if it's not already opened) and select the entry you want to enter (if KP2A did not already select the appropriate entry). Use the built-in keyboard or the clipboard to enter the password.
|
||||
### Autofill service based workflow
|
||||
If you have enabled the autofill service and open a (supported) app with a password field, a dropdown appears. Select "Fill with Keepass2Android" to select the appropriate entry. When you return to the app, the password and user field should be filled already.
|
||||
### KP2A based workflow for websites
|
||||
Open KP2A, open your database, select your entry (in this step, the notification bar items should show up already). Now click the URL link of the entry to open a browser window with the website. Use one of the methods described above to enter the credentials.
|
||||
### KP2A Keyboard based workflow
|
||||
When you are in a text field, you can use the Android icon in the notification bar to switch to the KP2A keyboard. Hit the KP2A key to select an icon. After it's selected, hit the KP2A key again to enter the desired field.
|
||||
|
||||
## Creating a new account
|
||||
Assume you want to create an account on a website. If you do not have a database yet, see above. As soon as you have a database, you may proceed as follows:
|
||||
* Go to the website you want to create the account for
|
||||
* Select Share/Share URL from the browser's menu and tap "Keepass2Android"
|
||||
* Log in to your database (if it's not already unlocked)
|
||||
* You will see the search result screen with "No search results"
|
||||
* Tap "Create entry for URL"
|
||||
* Choose the desired group, then tap the "+"-button to add an entry.
|
||||
* Tap the "..." button next to the password field to launch the password generator, create your password and then select "Accept"
|
||||
* Enter a name for the entry
|
||||
* Enter the username you want to use for the entry
|
||||
* Tap "Save" on the top
|
||||
* You should see notifications like "Entry is available through KP2A keyboard" and/or "Copy username/password to clipboard". If not, view the new entry by clicking it.
|
||||
* Return back to the browser.
|
||||
* Use the notifications to enter your new credentials. See "Getting passwords into the password fields" for more details.
|
||||
* If the user name you entered is not available or valid, choose a different one but copy it to clipboard. After creating the account, don't forget to update the new entry.
|
||||
|
||||
# Keepass2Android vs Keepass2Android Offline vs Keepassdroid
|
||||
What's the difference between these apps? There is a short comparison on [Comparison of Keepass apps for Android](Comparison-of-Keepass-apps-for-Android.md) to help you pick the best for you!
|
||||
|
||||
# Advanced topics
|
||||
## YubiKey NEO support for One-Time-Passwords
|
||||
Please see the [How to use Keepass2Android with YubiKey NEO](How-to-use-Keepass2Android-with-YubiKey-NEO.md) page.
|
||||
|
||||
## Advanced usage of the Keepass2Android keyboard
|
||||
Please see the [Advanced usage of the Keepass2Android keyboard](Advanced-usage-of-the-Keepass2Android-keyboard.md) page.
|
||||
|
||||
## Using Keepass2Android like an authenticator app to generate Time-based One-Time-Passwords (TOTPs)
|
||||
Please see [Generating TOTPs with Keepass2Android](Generating-TOTPs.md)
|
||||
|
||||
# FAQ
|
||||
|
||||
## Should I use the KP2A keyboard for entering passwords?
|
||||
The KP2A keyboard is meant to quickly "paste" or "type" values from your database to any text fields by using the KP2A icon. The QUERTY keyboard is just for convenience (if you just have the KP2A keyboard activated and need to enter a few letters). However, every other (trustworthy) keyboard is ok as well to enter sensitive information: Keyboard's aren't unsafe in Android. Only the clipboard is. Thus, the KP2A keyboard allows to get information out of the database without using the clipboard.
|
||||
**You can use any keyboard when you enter the main database password**
|
||||
|
||||
## Is it safe to store my kdbx file in the cloud?
|
||||
While it may happen that someone gets access to your kdbx file in the cloud, there is still no need to worry: the purpose of encryption is to protect the data even in case someone gets the kdbx file! As long as you are using a safe master key, you're safe! [Key files](https://keepass.info/help/base/keys.html#keyfiles) can help with securing the database even more.
|
||||
|
||||
## Doesn't Keepass2Android create automatic backups?
|
||||
Yes and no. Yes: Keepass2Android stores the last successfully opened file as a read-only backup locally on the phone (unless you disable this is in the settings). This should make sure that even if the file gets destroyed during a save operation or gets deleted by accident, you should always have a version that can be opened. (Don't mix this up with the internal file cache which is not meant as a backup and can easily be overwritten even with a corrupt file. This internal file cache is meant for providing writable access even when the original file is not reachable, e.g. when you're offline.)
|
||||
No: The local backup has two shortcomings: It is only one backup and does not allow to revert to older versions. So if you deleted an entry from the database, it might be deleted in the local backup soon as well. The even more important shortcoming is that it is just a local backup. It won't help when your phone gets lost or broken. Please create additional backups on seperate storage!
|
||||
|
||||
## How do I backup the database?
|
||||
If you have stored your database on the cloud, you might rely on your cloud storage providers backups. Make sure they allow you to revert to older revisions in case the file gets corrupted for some reason.
|
||||
If you are working with a local database file, make sure you create regular backups. I suggest you have an aumotated mechanism, e.g. with FolderSync (Lite) which can copy local files from your device to other locations, e.g. your PC in a local network. You can also use USB or tools like MyPhoneExploror to transfer data to your PC. Or, you use a removable storage like an SD card which you keep in a safe place after making the backup.
|
||||
In all cases, you need to verify that your backup is readable! It's even best to test this on another device (e.g. a PC), so you simulate the case that you may lose your phone.
|
||||
|
||||
## I can open my database with fingerprint, but don't remember my master password!
|
||||
It's time for action! As soon as possible, select Settings - Database - Export and choose unencrypted XML (don't put this on the cloud but on a local file). Transfer this file to a PC and import it to a new kdbx file, e.g. with Keepass2. Choose a new master password and make sure you don't forget this password!
|
||||
|
||||
## How can I transfer data from one device to another?
|
||||
* If you are about to get a new Android device, you should make sure you're not losing your passwords in the transition! The first thing you need to make sure is that you can access your .kdbx file (which stores the passwords) on the new device. If it is already stored in the cloud, you only need to make sure you know how to setup the cloud storage on the new device (it might require a password, so make sure you have access to that!).
|
||||
* If the .kdbx-file is stored locally on the old device, make sure you have an up-to-date backup (see above). You can then transfer that backup copy to the new device. (Note: transferring via USB causes data corruption in some cases, use MyPhoneExplorer or similar tools to be sure this does not happen.)
|
||||
* If you are securing your password database with a keyfile, also transfer this key file to the new device.
|
||||
* If you are opening your database with a fingerprint, make sure you also know the master password because fingerprint will not be available immediately on the new device.
|
||||
|
||||
## Why is Keepass2Android's apk so big?
|
||||
Please see [Keepass2Android Apk](Keepass2Android-Apk.md) for more information.
|
||||
|
||||
## I get a message "File is trashed" when reading or writing a file on Google Drive
|
||||
This happens because ocaml-fuse (I guess you are on Linux and use that) moves files to trash and then creates a new one instead of correctly updating the file on Google Drive (each file has a unique ID which Keepass2Android uses). Fortunately, this was fixed: https://github.com/astrada/google-drive-ocamlfuse/issues/494. After activating this option, please select "Change database" in KP2A, tap ,"Open file" and browse to the file on Google Drive again. After that, the message should no longer pop up.
|
||||
|
||||
## I get a message "The name must not be empty: null" when opening from Google Drive
|
||||
Please follow these steps:
|
||||
|
||||
* select "Change database" on the password screen, then "Open database" and browse to your file again
|
||||
* go to Android app settings and disable all permissions for the KP2A app. Then try again to open the database file.
|
||||
* reboot the device
|
||||
|
||||
(Before running the following steps, make sure you don't have local changes in your database which have not been synchronized with Google Drive (this can happen if you worked offline). If you have, please open the database from the local cache and go to settings - database settings - export database and make a backup copy of the data.)
|
||||
|
||||
* clear KP2A's app cache in the Android settings
|
||||
* uninstall & reinstall
|
||||
|
||||
One of these has helped all users so far, but unfortunately it's not totally clear to me why different steps are required (or nothing for most users).
|
||||
|
||||
# For developers
|
||||
If you are interested in adding new features, you have two options:
|
||||
Either your features can be implemented as a plug-in. Please see [How to create a plug-in?](How-to-create-a-plug-in_.md) for more information. Or you add the features directly in the source code of the projects and create a pull request.
|
||||
|
||||
If you want to build Keepass2Android, check the [build guide](Build.readme.md).
|
||||
This page has been moved to the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation)
|
@@ -1,53 +1 @@
|
||||
|
||||
## TOTP in brief
|
||||
TOTP stands for [Time-based One-Time Password algorithm](https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm) which is one of the most common way proposed by websites to do a [two-factor authentication (2FA)](https://en.wikipedia.org/wiki/Multi-factor_authentication).
|
||||
|
||||
On these websites, this option will often be mentioned in the 2FA configuration menu as things like "_use code generated by an application_", "_use [Google] Authenticator app_".
|
||||
|
||||
You're prompted to scan a QR code with the app, which essentially contains a code called "_seed_", usually with a form like "_AZER TYUI OPQS DFGH JKLM_", used to generate TOTPs. The seed can be also directly copied if there is no scanning option on the app.
|
||||
|
||||
Most common apps:
|
||||
|
||||
- Google Authenticator
|
||||
- Authy
|
||||
- Microsoft Authenticator
|
||||
- FreeOTP
|
||||
- LastPass Authenticator
|
||||
|
||||
## TOTP in KeePass and benefits
|
||||
In KeePass (by Dominik Reichl) there is are several ways to enable this Authenticator app ability:
|
||||
|
||||
- built-in TOTP support: https://keepass.info/help/base/placeholders.html#otp
|
||||
- [KeePassOTP plugin](https://keepass.info/plugins.html#kpotp)
|
||||
- [KeeOtp plugin](https://keepass.info/plugins.html#keeotp)
|
||||
- [KeeTrayTOTP plugin](https://keepass.info/plugins.html#keetraytotp) (note the name "_TrayTOTP_" on this one for later)
|
||||
|
||||
KeePassXC also supports TOTP: https://keepassxc.org/docs/KeePassXC_UserGuide#_adding_totp_to_an_entry
|
||||
|
||||
The greatest benefits are:
|
||||
|
||||
- the seed stays available contrary to the above apps (for which it's more or less hard to backup/restore/switch with another app)
|
||||
- TOTPs are available wherever the KeePass database is available. But conceptually it's not really 2FA anymore (all things are stored in the same place).
|
||||
|
||||
The different implementations use different ways of storing the TOTP seed (or secret, or key) and optional settings (e.g. the length of the TOTP to generate) within an entry inside the kdbx database. Keepass2Android attempts to be able to read the different formats, but can only write one:
|
||||
|
||||
## TOTP in Keepass2Android
|
||||
|
||||
If you use any of the tools mentioned above, you can set up TOTP entries with them. Keepass2Android can read those entries and generate TOTPs if any of the following styles are used:
|
||||
|
||||
* Keepass2 style: used when there are TimeOtp-Secret(-XXX) fields in the entry
|
||||
* KeeOtpPlugin style: used when there is an otp field containing a query string in the form of key=abc&step=X&size=Y (step and size are optional)
|
||||
* KeeWebOtp/Key Uri Format style: used when entry contains a URL starting with otpauth://totp/, e.g. otpauth://totp/?secret=abc (https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
|
||||
* KeeTrayTotp style:
|
||||
* requires a non-empty seed field (default key is "TOTP seed", can be changed in KP2A settings), value is base32 encoded data
|
||||
* requires a non-empty settings field (default key is "TOTP Settings", can be changed as well), value is expected to be a csv-separated array with [Duration];Length(;TimeCorrectionURL). Length is either an integer value or "S" to indicate Steam encoding
|
||||
|
||||
In order to view the generated TOTP code in KP2A, open the corresponding entry. You can then
|
||||
* use a dynamically generated field called "_TOTP_" containing the TOTP or
|
||||
* use the "Copy TOTP" button on the system notification for the selected entry or
|
||||
* switch to the KP2A keyboard and use the TOTP button to insert the TOTP value into the target app or browser
|
||||
|
||||
If you want to configure an entry to contain the TOTP fields, it is suggested to enter edit mode for the entry. Then click the "Configure TOTP" button. You can either enter the data manually or scan a QR code with the information.
|
||||
|
||||
### Spaces in otp field
|
||||
Make sure that the URI doesn't contain spaces, otherwise KeePass2Android will fail to generate TOTPs as a space is an invalid character. If your URIs have spaces, check [this comment](https://github.com/PhilippC/keepass2android/issues/1248#issuecomment-628035961)._
|
||||
This page has been moved to the [wiki](https://github.com/PhilippC/keepass2android/wiki/Generating-TOTPs)
|
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 87 KiB |
@@ -1,157 +0,0 @@
|
||||
# How to create a plug-in or connect from your app
|
||||
|
||||
Creating a plug-in for Keepass2Android or enabling your app to query credentials from Keepass2Android is pretty simple. Please follow the steps below to get started. In case you have any questions, please contact me.
|
||||
|
||||
## Preparations
|
||||
First check out the source code and import the Keepass2AndroidPluginSDK from [https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2](https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2/) into your workspace. You should be able to build this library project.
|
||||
|
||||
Now add a reference to the PluginSDK library from your existing app or add a new plug-in app and then add the reference.
|
||||
|
||||
## Authorization
|
||||
|
||||
Keepass2Android stores very sensitive user data and therefore implements a plug-in authorization scheme based on broadcasts sent between the plug-in and the host app (=Keepass2Android or Keepass2Android Offline). Before your app/plug-in gets any information from KP2A, the user will have to grant your app/plug-in access to KP2A. As not every app/plug-in requires access to all information, you must specify which scopes are required by your app. The implemented scopes can be found in [https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2/src/keepass2android/pluginsdk/Strings.java](https://github.com/PhilippC/keepass2android/tree/master/src/java/Keepass2AndroidPluginSDK2/src/keepass2android/pluginsdk/Strings.java).
|
||||
|
||||
To tell Kp2a that you're a plug-in, you need to add a simple BroadcastReceiver like this:
|
||||
|
||||
```java
|
||||
|
||||
public class PluginAAccessReceiver extends keepass2android.pluginsdk.PluginAccessBroadcastReceiver
|
||||
{
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getScopes() {
|
||||
ArrayList<String> scopes = new ArrayList<String>();
|
||||
scopes.add(Strings.SCOPE_DATABASE_ACTIONS);
|
||||
scopes.add(Strings.SCOPE_CURRENT_ENTRY);
|
||||
return scopes;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Here, you define the method getScopes where the list of scopes is created which must be granted by the user. The actual logic of the authorization process is implemented by the base class in the sdk.
|
||||
|
||||
In order to make this broadcast receiver visible to KP2A, add the following lines (probably with the name adapted to your class name) in the AndroidManifest.xml:
|
||||
|
||||
```xml
|
||||
<receiver android:name="PluginAAccessReceiver" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
```
|
||||
|
||||
Please also add a few strings in your resource files (e.g. strings.xml) with the following keys:
|
||||
|
||||
```xml
|
||||
<string name="kp2aplugin_title">The Great PluginA</string>
|
||||
<string name="kp2aplugin_shortdesc">Test plugin to demonstrate how plugins work</string>
|
||||
<string name="kp2aplugin_author">[your name here](your-name-here)</string>
|
||||
```
|
||||
These strings will be displayed to the user when KP2A asks if access should be granted.
|
||||
|
||||
## Modifying the entry view
|
||||
You can add menu options for the full entry or for individual fields of the entry when displayed to the user. This is done, for example, by the QR plugin ([https://play.google.com/store/apps/details?id=keepass2android.plugin.qr](https://play.google.com/store/apps/details?id=keepass2android.plugin.qr)).
|
||||
In addition, it is even possible to add new fields or modify existing fields. Please see the sample plugin "PluginA" for a simple example on how to do this:
|
||||
[https://github.com/PhilippC/keepass2android-sampleplugin/blob/main/src/keepass2android/plugina/PluginAAccessReceiver.java](https://github.com/PhilippC/keepass2android-sampleplugin/blob/main/src/keepass2android/plugina/PluginAAccessReceiver.java)
|
||||
|
||||
## Querying credentials
|
||||
KP2A 0.9.4 adds a great opportunity for third party apps: Instead of prompting the user to enter credentials or a passphrase, the app should try to get the data from KP2A if it is installed: If the user grants (or previously granted) access for the app, KP2A will automatically retrieve the matching entry. User action is only required if the KP2A database is locked (user will usually unlock it with the short QuickUnlock code) or if no matching entry is found (user can then create a new entry or select an existing one. in the latter case KP2A will offer to add entry information so that the entry will be found automatically next time).
|
||||
|
||||
To implement this, simply follow the steps descrIbed above in the sections Preparation and Authorization. Then, wherever appropriate in your app, do something like this:
|
||||
|
||||
```java
|
||||
try
|
||||
{
|
||||
PlaceholderFragment.this.startActivityForResult(
|
||||
Kp2aControl.getQueryEntryIntentForOwnPackage(),
|
||||
1);
|
||||
}
|
||||
catch (ActivityNotFoundException e)
|
||||
{
|
||||
Toast.makeText(
|
||||
PlaceholderFragment.this.getActivity(),
|
||||
"no KP2A host app found",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
(of course you can use `PacketManager` to check if the intent can be started instead of catching the `Exception`).
|
||||
|
||||
Instead of querying credentials associated with your own app, you might want to query other credentials as well. instead of `KpControl.getQueryEntryIntentForOwnPackage()` use
|
||||
`Kp2aControl.getQueryEntryIntent("google.com")`
|
||||
This requires \{"SCOPE_QUERY_CREDENTIALS (whereas getQueryEntryIntentForOwnPackage() requires SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)"\}.
|
||||
|
||||
The credential data can be retrieved in onActivityResult():
|
||||
|
||||
```java
|
||||
if ((requestCode == 1) //queryEntry for own package
|
||||
&& (resultCode == RESULT_OK)) // ensure user granted access and selected something
|
||||
{
|
||||
HashMap<String, String> credentials = Kp2aControl.getEntryFieldsFromIntent(data);
|
||||
if (!credentials.isEmpty())
|
||||
{
|
||||
//here we go!
|
||||
Toast.makeText(
|
||||
getActivity(),
|
||||
"retrieved credenitals! Username="+credentials.get(KeepassDefs.UserNameField),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that you get access to all strings (Title, Username, Password, URL, Notes + any user defined strings) in the entry. This may be in intersting in combination with the following section:
|
||||
|
||||
## Storing data in KP2A
|
||||
If you allow the user to set up an account in your app or create a password, e.g. for encryption, please add an option to store this data in the Keepass2Android database, as this will lead to great workflows for the user. It's as simple as
|
||||
|
||||
```java
|
||||
try {
|
||||
HashMap<String, String> fields = new HashMap<String, String>();
|
||||
//standard fields
|
||||
fields.put(KeepassDefs.TitleField, "plugin A");
|
||||
fields.put(KeepassDefs.UserNameField, "John Doe");
|
||||
fields.put(KeepassDefs.PasswordField, "top secret");
|
||||
//associate entry with our app. If we would require the URL field for a web URL,
|
||||
//this string could be added in any other (e.g. a custom) field
|
||||
fields.put(KeepassDefs.UrlField, "androidapp://"+getActivity().getPackageName());
|
||||
//custom field:
|
||||
fields.put(PLUGIN_A_PASSPHRASE, "some long text");
|
||||
//mark custom field as protected (i.e. display masked, enable memory protection in Keepass2)
|
||||
ArrayList<String> protectedFields = new ArrayList<String>();
|
||||
protectedFields.add(PLUGIN_A_PASSPHRASE);
|
||||
|
||||
//add to KP2A
|
||||
PlaceholderFragment.this.startActivityForResult(
|
||||
Kp2aControl.getAddEntryIntent(fields, protectedFields),
|
||||
2);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(
|
||||
PlaceholderFragment.this.getActivity(),
|
||||
"no KP2A host app found",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
```
|
||||
|
||||
Note that this does not even require access authorization because the user will actively save the entry anyways (after selecting the group where to create it.)
|
||||
|
||||
## Get information about database actions
|
||||
With {"SCOPE_DATABASE_ACTIONS"}, you will be informed when the user opens, closes, locks or unlocks the database including the file name information.
|
||||
|
||||
PluginA uses this to simply display a toast message in its ActionReceiver:
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected void dbAction(DatabaseAction db) {
|
||||
|
||||
Log.d("PluginA", db.getAction() + " in file " + db.getFileDisplayName() + " ("+db.getFilePath()+")");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Sample plugin
|
||||
Most example code from above is taken from the simple sample plugin "PluginA" as can be found on [https://keepass2android.codeplex.com/SourceControl/latest#src/java/PluginA/](https://keepass2android.codeplex.com/SourceControl/latest#src/java/PluginA/)
|
@@ -1,31 +0,0 @@
|
||||
<div class="wikidoc">
|
||||
<h1>How to use Keepass2Android with YubiKey NEO</h1>
|
||||
<p>Please refer to the documentation on the Keepass website (<a href="http://keepass.info/help/kb/yubikey.html">http://keepass.info/help/kb/yubikey.html</a>) or the Yubico website (<a href="http://www.yubico.com/applications/password-management/consumer/keepass/">http://www.yubico.com/applications/password-management/consumer/keepass/</a>)
|
||||
on how to set up a Keepass 2 database with Yubikey/OTP protection.<br>
|
||||
<br>
|
||||
After successful setup you should have the database file, e.g. yubi.kdbx, and the OTP auxiliary file, e.g. yubi.otp.xml, both in the same folder.<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_OTPAuxFile_2.png"><img title="OTPAuxFile" src="How to use Keepass2Android with YubiKey NEO_OTPAuxFile_thumb.png" alt="OTPAuxFile" width="513" height="40" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<p>Make sure you make <strong>both files</strong> available to Keepass2Android, e.g. by placing them both in your Dropbox.</p>
|
||||
<p>Now you should check your NDEF setup of the Yubikey NEO. Therefore, go to the Tools menu in the Yubico Personalization Utility. Select the same slot as used for OTPs with Keepass 2. The default setting for NDEF type and payload should work. If you experience
|
||||
problems, you may use the configuration as shown in this screenshot or simply press the “Reset” button:</p>
|
||||
<p><a href="How to use Keepass2Android with YubiKey NEO_image_2.png"><img title="image" src="How to use Keepass2Android with YubiKey NEO_image_thumb.png" alt="image" width="760" height="622" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<p><br>
|
||||
<br>
|
||||
In Keepass2Android, select "Open file" and locate your database file, e.g. yubi.kdbx.<br>
|
||||
<br>
|
||||
In the password screen under "Select master key type" select "Password + OTP".</p>
|
||||
<p><a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-50_2.png"><img title="Screenshot_2013-12-13-06-38-50" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-50_thumb.png" alt="Screenshot_2013-12-13-06-38-50" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<p>Click "Load auxiliary OTP file". This is required to load the information how many OTPs must be entered. As loading the file might require user action in some cases, this is not performed automatically.<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-12_2.png"><img title="Screenshot_2013-12-13-06-38-12" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-12_thumb.png" alt="Screenshot_2013-12-13-06-38-12" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a><br>
|
||||
After loading the OTP auxiliary file, you should see a few text fields for entering the OTPs. Now swipe your YubiKey NEO at the back of your Android device. If you have multiple apps which can handle NFC actions, you might be prompted to select which app to
|
||||
use. Select Keepass2Android in this case. Swipe your YubiKey again until all OTP fields are filled. Note: You don't need to select the next text field, this is done automatically!<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-36_2.png"><img title="Screenshot_2013-12-13-06-38-36" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-38-36_thumb.png" alt="Screenshot_2013-12-13-06-38-36" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a><br>
|
||||
Don't forget to also enter your password and click OK. You will see the “Saving auxiliary OTP file…” dialog. Note that there is some encryption envolved which is probably fast on your PC but might take some time on your mobile device. You
|
||||
can reduce the look-ahead window length to speed this up.<br>
|
||||
<a href="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-39-47_2.png"><img title="Screenshot_2013-12-13-06-39-47" src="How to use Keepass2Android with YubiKey NEO_Screenshot_2013-12-13-06-39-47_thumb.png" alt="Screenshot_2013-12-13-06-39-47" width="204" height="360" border="0" style="padding-top:0px; padding-left:0px; display:inline; padding-right:0px; border:0px"></a></p>
|
||||
<h2> </h2>
|
||||
<h2>A note about offline access</h2>
|
||||
<p>If your database is stored in the cloud or on the web, you can still access it if you have enabled file caching (which is on by default). With OTPs, this becomes a little bit more complicated: If you repeatedly open your datbase while being offline, the
|
||||
OTP counter stored on the Yubikey will be increased. Don’t forget to synchronize the database (which will also synchronize the OTP auxiliary file) as soon as possible to avoid problems with accessing your database on other devices! If you often need
|
||||
to open the database while you’re offline, consider increasing the look-ahead window length!</p>
|
||||
</div><div class="ClearBoth"></div>
|
@@ -1,25 +0,0 @@
|
||||
Keepass2Android's apk is pretty big, e.g. when comparing to Keepassdroid. The main difference is that Keepass2Android is built on Mono for Android. Mono is an open-source implementation of the Microsoft .Net Framework (installed on pretty much every Windows PC). On Windows, the .net framework requires several hundred MB (but only once, not for every application). On Android devices, Mono is not installed globally. Instead, it is packaged into every app. The more features from Mono are required, the bigger the package becomes.
|
||||
|
||||
Here's a list of what is contained in the Keepass2Android 0.9.1 application package:
|
||||
|
||||
```
|
||||
Mono for Android
|
||||
.net dlls 5.0 MB
|
||||
Runtime 2.5 MB
|
||||
Google libraries 0.8 MB
|
||||
(for Drive support)
|
||||
|
||||
Resources Strings, Icons.. 2.1 MB
|
||||
Password Font 0.2 MB
|
||||
Java Code including Dropbox 1.1 MB
|
||||
GDrive, SkyDrive
|
||||
libraries
|
||||
|
||||
Keepass library 0.2 MB
|
||||
Keepass2Android Code 0.3 MB
|
||||
Java/Mono bindings 0.5 MB
|
||||
|
||||
rest 0.3 MB
|
||||
|
||||
TOTAL 13 MB
|
||||
```
|
@@ -1,20 +0,0 @@
|
||||
Google has introduced the Android Autofill interface in Android 8. Keepass2Android supports this interface. In most Android apps and all Autofill-enabled browsers, this is the most convenient way of entering passwords. As soon as you focus a field, you will see a popup "Fill with Keepass2Android".
|
||||
|
||||
<img src="autofill-facebook.png" />
|
||||
|
||||
After clicking this popup, you can unlock your KP2A database. If automatic look up succeeds, KP2A will close automatically, if not you are prompted to select the entry you want to auto-fill. When returning to the target app, the fields should be filled automatically already.
|
||||
|
||||
As of January 2018, the following browsers are known to have Android Autofill support:
|
||||
|
||||
* Firefox Focus / Firefox Klar
|
||||
* Opera Mini
|
||||
|
||||
These browsers do not (yet) have autofill support:
|
||||
|
||||
* Google Chrome
|
||||
* Firefox for Android ([bugzilla entry](https://bugzilla.mozilla.org/show_bug.cgi?id=1352011))
|
||||
* Brave-Browser
|
||||
* Opera
|
||||
|
||||
Please use the Share-URL-feature and the built-in KP2A keyboard for these browsers.
|
||||
|
@@ -1,25 +1 @@
|
||||
# Who we are
|
||||
|
||||
Philipp Crocoll
|
||||
Wallonenstr. 4
|
||||
76297 Stutensee
|
||||
Germany
|
||||
|
||||
is the author of Keepass2Android and Keepass2Android Offline.
|
||||
|
||||
# What data is collected?
|
||||
|
||||
The contents of your password database is yours and is never collected by us. Keepass2Android stores this data on a location chosen by the user and encrypted in the Keepass database format. The app author does not have any access, neither to the files nor the contents. Depending on the user's choice of the storage location, the files may be stored on third-party servers like Dropbox or Google Drive.
|
||||
|
||||
Keepass2Android does not collect personal identifiable information. For debugging purposes, the user may activate creating a debug log. This collects data inside the app and is not accessible to any other app nor the author of the app, unless the user explicitly sends the debug log to the author. Debug logs usually do not contain personal identifiable information, except if such information is part of file or folder names. Debug logs will not be shared with third parties unless explicitly authorized by the sender.
|
||||
|
||||
# What Android permissions are required?
|
||||
|
||||
* **Internet** (Keepass2Android regular only): Required to allow the user to read/store password databases or key files on remote locations, e.g. Dropbox or via WebDav.
|
||||
* **Contacts/Accounts** (Keepass2Android regular only): Required by the Google Drive SDK. If you want to access files on Google Drive, you are prompted to select one of the Google Accounts on your phone to use. The permission is required to query the list of Google accounts on the device. Keepass2Android does not access your personal contacts.
|
||||
* **Storage**: Required to allow the user to read/store password databases or key files on the device locally.
|
||||
* **Fingerprint/Biometric**: Required if you want to use biometric unlock.
|
||||
* **Vibrate**: Required by the built-in keyboard (vibrate on key press)
|
||||
* **Camera**: Required for scanning OTP QR Codes
|
||||
* **Foreground service**: Required to keep the app alive for QuickUnlock (so you don't need to enter your full master password repeatedly)
|
||||
|
||||
This page has been moved to the [wiki](https://github.com/PhilippC/keepass2android/wiki/Privacy-Policy)
|
@@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
# What is Keepass2Android?
|
||||
Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database". This database is secured with a so-called master password. The master password typically is a strong password and can be complemented with a second factor for additional security.
|
||||
The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with Keepass 1 and Keepass 2 on Windows and KeepassX on Linux.
|
||||
Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database", secured with a strong key.
|
||||
The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with KeePass 2.x and KeepassXC on PCs as well as many other KeePass ports for a variety of platforms.
|
||||
|
||||
# Where to get it?
|
||||
Regular stable releases of Keepass2Android are available on [Google Play](https://play.google.com/store/apps/details?id=keepass2android.keepass2android).
|
||||
@@ -11,17 +11,13 @@ Regular stable releases of Keepass2Android are available on [Google Play](https:
|
||||
Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet).
|
||||
|
||||
# How can I contribute?
|
||||
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android)
|
||||
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android)
|
||||
* Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts.
|
||||
* [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC)
|
||||
* [Make a donation](http://philipp.crocoll.net/donate.php)
|
||||
* [Make a donation](https://philipp.crocoll.net/donate.php)
|
||||
|
||||
# How do I learn more?
|
||||
Please see the [documentation](Documentation.md).
|
||||
Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information.
|
||||
|
||||
# How do I build the project?
|
||||
If you want to build Keepass2Android, check the [build guide](Build.readme.md).
|
||||
|
||||
The project homepage is https://philipp.crocoll.net/keepass2android/index.php
|
||||
|
||||
<img src="https://github.com/PhilippC/keepass2android/actions/workflows/build.yml/badge.svg" alt="build status" /> [Build status](https://github.com/PhilippC/keepass2android/actions)
|
||||
|
@@ -1,72 +0,0 @@
|
||||
# SFTP Open/Create Database Credentials Documentation
|
||||
|
||||
## Basic Settings
|
||||
* **Host** -- the hostname or IP address of the SFTP server to connect to
|
||||
* **Port** -- the listening TCP port of the SFTP server to connect to (default: 22)
|
||||
* **Username** -- the user/account name on the SFTP server that has access to the database
|
||||
* **Initial directory** -- The path on the SFTP server that will be used as a starting point when choosing the remote database file
|
||||
|
||||
### Authentication Modes
|
||||
|
||||
#### Password
|
||||
Authenticate using a password
|
||||
|
||||
* **Password** -- the password associated with **username** used to log into the SFTP server
|
||||
|
||||
#### K2A Private/Public Key
|
||||
Authenticate using a private/public key pair that is generated internally by KP2A
|
||||
|
||||
* **SEND PUBLIC KEY...** -- Opens a standard Android "Share" screen containing the KP2A public key content. This allows for the public key to be sent via email, SMS, etc. This public key will need to be added to the SFTP server's user's "authorized keys" to allow private/public key authentication.
|
||||
|
||||
#### Custom Private Key
|
||||
Authenticate using an existing private/public key pair. Use this option instead of *K2A Private/Public Key* if you wish to use a key pair that is already set up for this **username** on the SFTP server.
|
||||
|
||||
* **Selected private key** -- a combo-box containing a list of custom private keys that KP2A knows about, and a special `[Add new...]` option.
|
||||
##### Add A New Private Key
|
||||
* Select `[Add new...]`
|
||||
* Enter a name for the new key in **New key name**
|
||||
* Enter the private key contents (text) into **New key content**. **TIP:** The easiest way to accomplish this is to open the private key file in a text editor on the device, **Select All**, **Copy** to the clipboard, and paste it into **New key content**.
|
||||
* Tap **SAVE PRIVATE KEY** to add the new key to the known list.
|
||||
|
||||
##### Use An Existing Private Key
|
||||
* To use a private key that has already been imported into KP2A, simply select it from the list of keys.
|
||||
|
||||
##### Remove An Existing Key
|
||||
* To remove a private that has been imported into KP2A, select it from the list and tap **DELETE PRIVATE KEY**.
|
||||
|
||||
A **key passphrase** can be supplied (if the key pair requires it)
|
||||
|
||||
## Advanced Settings
|
||||
* **Connection timeout seconds** -- the number of seconds to wait for a connection to the server before giving up and considering the server as unavailable/unreachable
|
||||
|
||||
### Key Algorithm Manipulation
|
||||
**NOTE: It is very rare that these fields need to be (or should be) specified. Use at your own risk!**
|
||||
|
||||
* **Key Exchange (KEX) Algorithm(s)** -- Explicitly set or modify the ordered list of Key Exchange algorithms that the SSH/SFTP client library will try to use
|
||||
* **Server Host Key Algorithm(s)** -- Explicitly set or modify the ordered list of Server Host Key algorithms that the SSH/SFTP client library will try to use
|
||||
|
||||
#### How It Works
|
||||
The SSH/SFTP client has a pre-defined ordered list of algorithm names that it will use to negotiate with the server to handle key exchange. In rare cases there are compatibility issues where Android OS has not properly implemented full support for algorithms listed. This can result in a connection failure, even if there is a suitable algorithm available (of lesser priority in the list).
|
||||
|
||||
The fields listed above allow these lists to be manipulated in the following ways to overcome/workaround such problems. The value is a comma-separated list of "algorithm spec" entries. Specs can be one of:
|
||||
|
||||
* Direct replacement of values -- Ex: `primary_alg,secondary_alg`
|
||||
* Prepend to values -- Ex: `+try_first_alg`
|
||||
* Append to values -- Ex: `try_last_alg+`
|
||||
* Remove a specific value -- Ex: `-bad_alg`
|
||||
* Remove values matching prefix -- Ex: `-bad_starting_with*`
|
||||
* Remove values matching suffix -- Ex: `-*bad_ending_with`
|
||||
* Remove values matching substring -- Ex: `-*bad_middle*`
|
||||
* Remove values matching prefix and suffix -- Ex: `-alg_begin*end`
|
||||
|
||||
For example, assume the system's KEX algorithm list is:
|
||||
`ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256`
|
||||
|
||||
These are various outcomes (user KEX field -> result):
|
||||
|
||||
* Prefix removal: `-ec*` --> `diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256`
|
||||
* Suffix removal, appending: `-*256,+first_alg,almost_last_alg+,last_alg+` --> `first_alg,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,almost_last_alg,last_alg`
|
||||
* Direct replacement: `first_alg,middle_alg,last_alg` --> `first_alg,middle_alg,last_alg`
|
||||
|
||||
## Selecting A Database
|
||||
Once all applicable fields have been entered and/or options selected, tapping **OK** will attempt to connect to the SFTP server. First time connections may pop up a dialog window asking to accept the host's authenticity (tap **yes** if the host is trusted), as well as potentially creating a new `known_hosts` file (tap **yes** to do so). If the connection is successful, a remote file browser screen will open. Navigate and select the Keepass database to open.
|
@@ -1 +0,0 @@
|
||||
theme: jekyll-theme-slate
|
Before Width: | Height: | Size: 69 KiB |
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -34,5 +34,6 @@
|
||||
<AndroidLibrary Bind="False" Update="okhttp-digest-3.1.0.jar" />
|
||||
<AndroidLibrary Bind="False" Update="okio-3.6.0.jar" />
|
||||
<AndroidLibrary Bind="False" Update="okio-jvm-3.6.0.jar" />
|
||||
<AndroidLibrary Bind="False" Update="jsch-2.27.2.jar" />
|
||||
</ItemGroup>
|
||||
</Project>
|
BIN
src/JavaFileStorageBindings/jsch-2.27.2.jar
Normal file
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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
|
||||
@@ -40,8 +40,8 @@ namespace KeePassLib.Cryptography
|
||||
Null = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A variant of the ARCFour algorithm (RC4 incompatible).
|
||||
/// </summary>
|
||||
/// A variant of the ArcFour algorithm (RC4 incompatible).
|
||||
/// Insecure; for backward compatibility only.
|
||||
/// </summary>
|
||||
ArcFourVariant = 1,
|
||||
|
||||
@@ -66,68 +66,72 @@ namespace KeePassLib.Cryptography
|
||||
/// </summary>
|
||||
public sealed class CryptoRandomStream : IDisposable
|
||||
{
|
||||
private readonly CrsAlgorithm m_crsAlgorithm;
|
||||
private readonly CrsAlgorithm m_alg;
|
||||
private bool m_bDisposed = false;
|
||||
|
||||
private byte[] m_pbState = null;
|
||||
private readonly byte[] m_pbKey = null;
|
||||
private readonly byte[] m_pbIV = null;
|
||||
|
||||
private readonly ChaCha20Cipher m_chacha20 = null;
|
||||
private readonly Salsa20Cipher m_salsa20 = null;
|
||||
|
||||
private readonly byte[] m_pbState = null;
|
||||
private byte m_i = 0;
|
||||
private byte m_j = 0;
|
||||
|
||||
private Salsa20Cipher m_salsa20 = null;
|
||||
private ChaCha20Cipher m_chacha20 = null;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new cryptographically secure random stream object.
|
||||
/// </summary>
|
||||
/// <param name="genAlgorithm">Algorithm to use.</param>
|
||||
/// <param name="pbKey">Initialization key. Must not be <c>null</c> and
|
||||
/// must contain at least 1 byte.</param>
|
||||
/// <param name="a">Algorithm to use.</param>
|
||||
/// <param name="pbKey">Initialization key. Must not be <c>null</c>
|
||||
/// and must contain at least 1 byte.</param>
|
||||
public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
|
||||
{
|
||||
if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
|
||||
/// <exception cref="System.ArgumentNullException">Thrown if the
|
||||
if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); }
|
||||
|
||||
int cbKey = pbKey.Length;
|
||||
if(cbKey <= 0)
|
||||
if (cbKey <= 0)
|
||||
{
|
||||
Debug.Assert(false); // Need at least one byte
|
||||
throw new ArgumentOutOfRangeException("pbKey");
|
||||
}
|
||||
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
|
||||
m_crsAlgorithm = a;
|
||||
/// <exception cref="System.ArgumentException">Thrown if the
|
||||
if(a == CrsAlgorithm.ChaCha20)
|
||||
|
||||
m_alg = a;
|
||||
|
||||
if (a == CrsAlgorithm.ChaCha20)
|
||||
{
|
||||
byte[] pbKey32 = new byte[32];
|
||||
byte[] pbIV12 = new byte[12];
|
||||
/// <paramref name="pbKey" /> parameter contains no bytes or the
|
||||
using(SHA512Managed h = new SHA512Managed())
|
||||
m_pbKey = new byte[32];
|
||||
m_pbIV = new byte[12];
|
||||
|
||||
using (SHA512Managed h = new SHA512Managed())
|
||||
{
|
||||
byte[] pbHash = h.ComputeHash(pbKey);
|
||||
Array.Copy(pbHash, pbKey32, 32);
|
||||
Array.Copy(pbHash, 32, pbIV12, 0, 12);
|
||||
Array.Copy(pbHash, m_pbKey, 32);
|
||||
Array.Copy(pbHash, 32, m_pbIV, 0, 12);
|
||||
MemUtil.ZeroByteArray(pbHash);
|
||||
}
|
||||
/// algorithm is unknown.</exception>
|
||||
m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true);
|
||||
|
||||
m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true);
|
||||
}
|
||||
else if(a == CrsAlgorithm.Salsa20)
|
||||
else if (a == CrsAlgorithm.Salsa20)
|
||||
{
|
||||
byte[] pbKey32 = CryptoUtil.HashSha256(pbKey);
|
||||
byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
|
||||
m_pbKey = CryptoUtil.HashSha256(pbKey);
|
||||
m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B,
|
||||
0x97, 0x20, 0x5D, 0x2A }; // Unique constant
|
||||
|
||||
m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8);
|
||||
m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV);
|
||||
}
|
||||
else if(a == CrsAlgorithm.ArcFourVariant)
|
||||
else if (a == CrsAlgorithm.ArcFourVariant)
|
||||
{
|
||||
// Fill the state linearly
|
||||
m_pbState = new byte[256];
|
||||
for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w;
|
||||
for (int w = 0; w < 256; ++w) m_pbState[w] = (byte)w;
|
||||
|
||||
unchecked
|
||||
{
|
||||
byte j = 0, t;
|
||||
int inxKey = 0;
|
||||
for(int w = 0; w < 256; ++w) // Key setup
|
||||
for (int w = 0; w < 256; ++w) // Key setup
|
||||
{
|
||||
j += (byte)(m_pbState[w] + pbKey[inxKey]);
|
||||
|
||||
@@ -136,7 +140,7 @@ namespace KeePassLib.Cryptography
|
||||
m_pbState[j] = t;
|
||||
|
||||
++inxKey;
|
||||
if(inxKey >= cbKey) inxKey = 0;
|
||||
if (inxKey >= cbKey) inxKey = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,19 +161,24 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if(disposing)
|
||||
if (disposing)
|
||||
{
|
||||
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20)
|
||||
if (m_alg == CrsAlgorithm.ChaCha20)
|
||||
m_chacha20.Dispose();
|
||||
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20)
|
||||
else if (m_alg == CrsAlgorithm.Salsa20)
|
||||
m_salsa20.Dispose();
|
||||
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
|
||||
else if (m_alg == CrsAlgorithm.ArcFourVariant)
|
||||
{
|
||||
MemUtil.ZeroByteArray(m_pbState);
|
||||
m_i = 0;
|
||||
m_j = 0;
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
|
||||
if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey);
|
||||
if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV);
|
||||
|
||||
m_bDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,23 +189,24 @@ namespace KeePassLib.Cryptography
|
||||
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
|
||||
public byte[] GetRandomBytes(uint uRequestedCount)
|
||||
{
|
||||
if(uRequestedCount == 0) return MemUtil.EmptyByteArray;
|
||||
if (m_bDisposed) throw new ObjectDisposedException(null);
|
||||
|
||||
if(uRequestedCount > (uint)int.MaxValue)
|
||||
if (uRequestedCount == 0) return MemUtil.EmptyByteArray;
|
||||
if (uRequestedCount > (uint)int.MaxValue)
|
||||
throw new ArgumentOutOfRangeException("uRequestedCount");
|
||||
int cb = (int)uRequestedCount;
|
||||
|
||||
byte[] pbRet = new byte[cb];
|
||||
|
||||
if(m_crsAlgorithm == CrsAlgorithm.ChaCha20)
|
||||
if (m_alg == CrsAlgorithm.ChaCha20)
|
||||
m_chacha20.Encrypt(pbRet, 0, cb);
|
||||
else if(m_crsAlgorithm == CrsAlgorithm.Salsa20)
|
||||
else if (m_alg == CrsAlgorithm.Salsa20)
|
||||
m_salsa20.Encrypt(pbRet, 0, cb);
|
||||
else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant)
|
||||
else if (m_alg == CrsAlgorithm.ArcFourVariant)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
for(int w = 0; w < cb; ++w)
|
||||
for (int w = 0; w < cb; ++w)
|
||||
{
|
||||
++m_i;
|
||||
m_j += m_pbState[m_i];
|
||||
@@ -221,6 +231,25 @@ namespace KeePassLib.Cryptography
|
||||
return MemUtil.BytesToUInt64(pb);
|
||||
}
|
||||
|
||||
internal ulong GetRandomUInt64(ulong uMaxExcl)
|
||||
{
|
||||
if (uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); }
|
||||
|
||||
ulong uGen, uRem;
|
||||
do
|
||||
{
|
||||
uGen = GetRandomUInt64();
|
||||
uRem = uGen % uMaxExcl;
|
||||
}
|
||||
while ((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL)));
|
||||
// This ensures that the last number of the block (i.e.
|
||||
// (uGen - uRem) + (uMaxExcl - 1)) is generatable;
|
||||
// for signed longs, overflow to negative number:
|
||||
// while((uGen - uRem) + (uMaxExcl - 1) < 0);
|
||||
|
||||
return uRem;
|
||||
}
|
||||
|
||||
#if CRSBENCHMARK
|
||||
public static string Benchmark()
|
||||
{
|
||||
@@ -237,21 +266,20 @@ namespace KeePassLib.Cryptography
|
||||
return str;
|
||||
}
|
||||
|
||||
private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize)
|
||||
private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData)
|
||||
{
|
||||
byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 };
|
||||
|
||||
int nStart = Environment.TickCount;
|
||||
int tStart = Environment.TickCount;
|
||||
for(int i = 0; i < nRounds; ++i)
|
||||
{
|
||||
using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey))
|
||||
using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey))
|
||||
{
|
||||
c.GetRandomBytes((uint)nDataSize);
|
||||
crs.GetRandomBytes((uint)cbData);
|
||||
}
|
||||
}
|
||||
int nEnd = Environment.TickCount;
|
||||
|
||||
return (nEnd - nStart);
|
||||
return (Environment.TickCount - tStart);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
{
|
||||
internal static class CharSetBasedGenerator
|
||||
{
|
||||
internal static PwgError Generate(out ProtectedString psOut,
|
||||
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
psOut = ProtectedString.Empty;
|
||||
if(pwProfile.Length == 0) return PwgError.Success;
|
||||
|
||||
PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString());
|
||||
char[] vGenerated = new char[pwProfile.Length];
|
||||
|
||||
PwGenerator.PrepareCharSet(pcs, pwProfile);
|
||||
|
||||
for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex)
|
||||
{
|
||||
char ch = PwGenerator.GenerateCharacter(pwProfile, pcs,
|
||||
crsRandomSource);
|
||||
|
||||
if(ch == char.MinValue)
|
||||
{
|
||||
MemUtil.ZeroArray<char>(vGenerated);
|
||||
return PwgError.TooFewCharacters;
|
||||
}
|
||||
|
||||
vGenerated[nIndex] = ch;
|
||||
}
|
||||
|
||||
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated);
|
||||
psOut = new ProtectedString(true, pbUtf8);
|
||||
MemUtil.ZeroByteArray(pbUtf8);
|
||||
MemUtil.ZeroArray<char>(vGenerated);
|
||||
|
||||
return PwgError.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
{
|
||||
internal static class PatternBasedGenerator
|
||||
{
|
||||
internal static PwgError Generate(out ProtectedString psOut,
|
||||
PwProfile pwProfile, CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
psOut = ProtectedString.Empty;
|
||||
LinkedList<char> vGenerated = new LinkedList<char>();
|
||||
PwCharSet pcsCurrent = new PwCharSet();
|
||||
PwCharSet pcsCustom = new PwCharSet();
|
||||
PwCharSet pcsUsed = new PwCharSet();
|
||||
bool bInCharSetDef = false;
|
||||
|
||||
string strPattern = ExpandPattern(pwProfile.Pattern);
|
||||
if(strPattern.Length == 0) return PwgError.Success;
|
||||
|
||||
CharStream csStream = new CharStream(strPattern);
|
||||
char ch = csStream.ReadChar();
|
||||
|
||||
while(ch != char.MinValue)
|
||||
{
|
||||
pcsCurrent.Clear();
|
||||
|
||||
bool bGenerateChar = false;
|
||||
|
||||
if(ch == '\\')
|
||||
{
|
||||
ch = csStream.ReadChar();
|
||||
if(ch == char.MinValue) // Backslash at the end
|
||||
{
|
||||
vGenerated.AddLast('\\');
|
||||
break;
|
||||
}
|
||||
|
||||
if(bInCharSetDef) pcsCustom.Add(ch);
|
||||
else
|
||||
{
|
||||
vGenerated.AddLast(ch);
|
||||
pcsUsed.Add(ch);
|
||||
}
|
||||
}
|
||||
else if(ch == '[')
|
||||
{
|
||||
pcsCustom.Clear();
|
||||
bInCharSetDef = true;
|
||||
}
|
||||
else if(ch == ']')
|
||||
{
|
||||
pcsCurrent.Add(pcsCustom.ToString());
|
||||
|
||||
bInCharSetDef = false;
|
||||
bGenerateChar = true;
|
||||
}
|
||||
else if(bInCharSetDef)
|
||||
{
|
||||
if(pcsCustom.AddCharSet(ch) == false)
|
||||
pcsCustom.Add(ch);
|
||||
}
|
||||
else if(pcsCurrent.AddCharSet(ch) == false)
|
||||
{
|
||||
vGenerated.AddLast(ch);
|
||||
pcsUsed.Add(ch);
|
||||
}
|
||||
else bGenerateChar = true;
|
||||
|
||||
if(bGenerateChar)
|
||||
{
|
||||
PwGenerator.PrepareCharSet(pcsCurrent, pwProfile);
|
||||
|
||||
if(pwProfile.NoRepeatingCharacters)
|
||||
pcsCurrent.Remove(pcsUsed.ToString());
|
||||
|
||||
char chGen = PwGenerator.GenerateCharacter(pwProfile,
|
||||
pcsCurrent, crsRandomSource);
|
||||
|
||||
if(chGen == char.MinValue) return PwgError.TooFewCharacters;
|
||||
|
||||
vGenerated.AddLast(chGen);
|
||||
pcsUsed.Add(chGen);
|
||||
}
|
||||
|
||||
ch = csStream.ReadChar();
|
||||
}
|
||||
|
||||
if(vGenerated.Count == 0) return PwgError.Success;
|
||||
|
||||
char[] vArray = new char[vGenerated.Count];
|
||||
vGenerated.CopyTo(vArray, 0);
|
||||
|
||||
if(pwProfile.PatternPermutePassword)
|
||||
PwGenerator.ShufflePassword(vArray, crsRandomSource);
|
||||
|
||||
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray);
|
||||
psOut = new ProtectedString(true, pbUtf8);
|
||||
MemUtil.ZeroByteArray(pbUtf8);
|
||||
MemUtil.ZeroArray<char>(vArray);
|
||||
vGenerated.Clear();
|
||||
|
||||
return PwgError.Success;
|
||||
}
|
||||
|
||||
private static string ExpandPattern(string strPattern)
|
||||
{
|
||||
Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty;
|
||||
string str = strPattern;
|
||||
|
||||
while(true)
|
||||
{
|
||||
int nOpen = FindFirstUnescapedChar(str, '{');
|
||||
int nClose = FindFirstUnescapedChar(str, '}');
|
||||
|
||||
if((nOpen >= 0) && (nOpen < nClose))
|
||||
{
|
||||
string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1);
|
||||
str = str.Remove(nOpen, nClose - nOpen + 1);
|
||||
|
||||
uint uRepeat;
|
||||
if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1))
|
||||
{
|
||||
if(uRepeat == 0)
|
||||
str = str.Remove(nOpen - 1, 1);
|
||||
else
|
||||
str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1));
|
||||
}
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private static int FindFirstUnescapedChar(string str, char ch)
|
||||
{
|
||||
for(int i = 0; i < str.Length; ++i)
|
||||
{
|
||||
char chCur = str[i];
|
||||
|
||||
if(chCur == '\\') ++i; // Next is escaped, skip it
|
||||
else if(chCur == ch) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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
|
||||
@@ -19,122 +19,81 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using KeePassLib.Utility;
|
||||
|
||||
namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
{
|
||||
public sealed class PwCharSet
|
||||
public sealed class PwCharSet : IEquatable<PwCharSet>
|
||||
{
|
||||
public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
public const string LowerCase = "abcdefghijklmnopqrstuvwxyz";
|
||||
public const string Digits = "0123456789";
|
||||
public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz";
|
||||
public static readonly string Digits = "0123456789";
|
||||
|
||||
public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
|
||||
public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
|
||||
public const string UpperVowels = "AEIOU";
|
||||
public const string LowerVowels = "aeiou";
|
||||
public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ";
|
||||
public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz";
|
||||
public static readonly string UpperVowels = "AEIOU";
|
||||
public static readonly string LowerVowels = "aeiou";
|
||||
|
||||
public const string Punctuation = @",.;:";
|
||||
public const string Brackets = @"[]{}()<>";
|
||||
public static readonly string Punctuation = ",.;:";
|
||||
public static readonly string Brackets = @"[]{}()<>";
|
||||
|
||||
public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
||||
public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~";
|
||||
public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
||||
|
||||
public const string UpperHex = "0123456789ABCDEF";
|
||||
public const string LowerHex = "0123456789abcdef";
|
||||
public static readonly string UpperHex = "0123456789ABCDEF";
|
||||
public static readonly string LowerHex = "0123456789abcdef";
|
||||
|
||||
public const string Invalid = "\t\r\n";
|
||||
public const string LookAlike = @"O0l1I|";
|
||||
|
||||
internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
|
||||
|
||||
private const int CharTabSize = (0x10000 / 8);
|
||||
|
||||
private List<char> m_vChars = new List<char>();
|
||||
private byte[] m_vTab = new byte[CharTabSize];
|
||||
|
||||
private static string m_strHighAnsi = null;
|
||||
public static string HighAnsiChars
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_strHighAnsi == null) { new PwCharSet(); } // Create string
|
||||
Debug.Assert(m_strHighAnsi != null);
|
||||
return m_strHighAnsi;
|
||||
}
|
||||
}
|
||||
|
||||
private static string m_strSpecial = null;
|
||||
public static string SpecialChars
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_strSpecial == null) { new PwCharSet(); } // Create string
|
||||
Debug.Assert(m_strSpecial != null);
|
||||
return m_strSpecial;
|
||||
}
|
||||
}
|
||||
public static readonly string LookAlike = "O0Il1|";
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty character set collection object.
|
||||
/// Latin-1 Supplement except U+00A0 (NBSP) and U+00AD (SHY).
|
||||
/// </summary>
|
||||
public static readonly string Latin1S =
|
||||
"\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7" +
|
||||
"\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF" +
|
||||
"\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7" +
|
||||
"\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF" +
|
||||
"\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7" +
|
||||
"\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF" +
|
||||
"\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7" +
|
||||
"\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF" +
|
||||
"\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7" +
|
||||
"\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF" +
|
||||
"\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7" +
|
||||
"\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF";
|
||||
|
||||
// internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits;
|
||||
|
||||
[Obsolete]
|
||||
public static string SpecialChars { get { return PwCharSet.Special; } }
|
||||
[Obsolete]
|
||||
public static string HighAnsiChars { get { return PwCharSet.Latin1S; } }
|
||||
|
||||
private readonly List<char> m_lChars = new List<char>();
|
||||
private readonly byte[] m_vTab = new byte[0x10000 / 8];
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty character set.
|
||||
/// </summary>
|
||||
public PwCharSet()
|
||||
{
|
||||
Initialize(true);
|
||||
Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2));
|
||||
}
|
||||
|
||||
public PwCharSet(string strCharSet)
|
||||
{
|
||||
Initialize(true);
|
||||
Add(strCharSet);
|
||||
}
|
||||
|
||||
private PwCharSet(bool bFullInitialize)
|
||||
{
|
||||
Initialize(bFullInitialize);
|
||||
}
|
||||
|
||||
private void Initialize(bool bFullInitialize)
|
||||
{
|
||||
Clear();
|
||||
|
||||
if(!bFullInitialize) return;
|
||||
|
||||
if(m_strHighAnsi == null)
|
||||
{
|
||||
StringBuilder sbHighAnsi = new StringBuilder();
|
||||
// [U+0080, U+009F] are C1 control characters,
|
||||
// U+00A0 is non-breaking space
|
||||
for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch)
|
||||
sbHighAnsi.Append(ch);
|
||||
// U+00AD is soft hyphen (format character)
|
||||
for(char ch = '\u00AE'; ch < '\u00FF'; ++ch)
|
||||
sbHighAnsi.Append(ch);
|
||||
sbHighAnsi.Append('\u00FF');
|
||||
|
||||
m_strHighAnsi = sbHighAnsi.ToString();
|
||||
}
|
||||
|
||||
if(m_strSpecial == null)
|
||||
{
|
||||
PwCharSet pcs = new PwCharSet(false);
|
||||
pcs.AddRange('!', '/');
|
||||
pcs.AddRange(':', '@');
|
||||
pcs.AddRange('[', '`');
|
||||
pcs.Add(@"|~");
|
||||
pcs.Remove(@"-_ ");
|
||||
pcs.Remove(PwCharSet.Brackets);
|
||||
|
||||
m_strSpecial = pcs.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of characters in this set.
|
||||
/// </summary>
|
||||
public uint Size
|
||||
{
|
||||
get { return (uint)m_vChars.Count; }
|
||||
get { return (uint)m_lChars.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,19 +106,39 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
{
|
||||
get
|
||||
{
|
||||
if(uPos >= (uint)m_vChars.Count)
|
||||
if (uPos >= (uint)m_lChars.Count)
|
||||
throw new ArgumentOutOfRangeException("uPos");
|
||||
|
||||
return m_vChars[(int)uPos];
|
||||
return m_lChars[(int)uPos];
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(PwCharSet other)
|
||||
{
|
||||
if (object.ReferenceEquals(other, this)) return true;
|
||||
if (object.ReferenceEquals(other, null)) return false;
|
||||
|
||||
if (m_lChars.Count != other.m_lChars.Count) return false;
|
||||
|
||||
return MemUtil.ArraysEqual(m_vTab, other.m_vTab);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as PwCharSet);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all characters from this set.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_vChars.Clear();
|
||||
m_lChars.Clear();
|
||||
Array.Clear(m_vTab, 0, m_vTab.Length);
|
||||
}
|
||||
|
||||
@@ -171,11 +150,11 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
public bool Contains(string strCharacters)
|
||||
{
|
||||
Debug.Assert(strCharacters != null);
|
||||
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
|
||||
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
|
||||
|
||||
foreach(char ch in strCharacters)
|
||||
foreach (char ch in strCharacters)
|
||||
{
|
||||
if(!Contains(ch)) return false;
|
||||
if (!Contains(ch)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -187,11 +166,11 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
/// <param name="ch">Character to add.</param>
|
||||
public void Add(char ch)
|
||||
{
|
||||
if(ch == char.MinValue) { Debug.Assert(false); return; }
|
||||
if (ch == char.MinValue) { Debug.Assert(false); return; }
|
||||
|
||||
if(!Contains(ch))
|
||||
if (!Contains(ch))
|
||||
{
|
||||
m_vChars.Add(ch);
|
||||
m_lChars.Add(ch);
|
||||
m_vTab[ch / 8] |= (byte)(1 << (ch % 8));
|
||||
}
|
||||
}
|
||||
@@ -203,11 +182,9 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
public void Add(string strCharSet)
|
||||
{
|
||||
Debug.Assert(strCharSet != null);
|
||||
if(strCharSet == null) throw new ArgumentNullException("strCharSet");
|
||||
if (strCharSet == null) throw new ArgumentNullException("strCharSet");
|
||||
|
||||
m_vChars.Capacity = m_vChars.Count + strCharSet.Length;
|
||||
|
||||
foreach(char ch in strCharSet)
|
||||
foreach (char ch in strCharSet)
|
||||
Add(ch);
|
||||
}
|
||||
|
||||
@@ -226,9 +203,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
|
||||
public void AddRange(char chMin, char chMax)
|
||||
{
|
||||
m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1;
|
||||
|
||||
for(char ch = chMin; ch < chMax; ++ch)
|
||||
for (char ch = chMin; ch < chMax; ++ch)
|
||||
Add(ch);
|
||||
|
||||
Add(chMax);
|
||||
@@ -238,14 +213,16 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
{
|
||||
bool bResult = true;
|
||||
|
||||
switch(chCharSetIdentifier)
|
||||
switch (chCharSetIdentifier)
|
||||
{
|
||||
case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break;
|
||||
case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
|
||||
case 'A':
|
||||
Add(PwCharSet.LowerCase, PwCharSet.UpperCase,
|
||||
PwCharSet.Digits); break;
|
||||
case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break;
|
||||
case 'c': Add(PwCharSet.LowerConsonants); break;
|
||||
case 'C': Add(PwCharSet.LowerConsonants,
|
||||
case 'C':
|
||||
Add(PwCharSet.LowerConsonants,
|
||||
PwCharSet.UpperConsonants); break;
|
||||
case 'z': Add(PwCharSet.UpperConsonants); break;
|
||||
case 'd': Add(PwCharSet.Digits); break; // Digit
|
||||
@@ -257,12 +234,13 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
case 'p': Add(PwCharSet.Punctuation); break;
|
||||
case 'b': Add(PwCharSet.Brackets); break;
|
||||
case 's': Add(PwCharSet.PrintableAsciiSpecial); break;
|
||||
case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
|
||||
case 'S':
|
||||
Add(PwCharSet.UpperCase, PwCharSet.LowerCase);
|
||||
Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break;
|
||||
case 'v': Add(PwCharSet.LowerVowels); break;
|
||||
case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break;
|
||||
case 'Z': Add(PwCharSet.UpperVowels); break;
|
||||
case 'x': Add(m_strHighAnsi); break;
|
||||
case 'x': Add(PwCharSet.Latin1S); break;
|
||||
default: bResult = false; break;
|
||||
}
|
||||
|
||||
@@ -272,18 +250,18 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
public bool Remove(char ch)
|
||||
{
|
||||
m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8)));
|
||||
return m_vChars.Remove(ch);
|
||||
return m_lChars.Remove(ch);
|
||||
}
|
||||
|
||||
public bool Remove(string strCharacters)
|
||||
{
|
||||
Debug.Assert(strCharacters != null);
|
||||
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
|
||||
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
|
||||
|
||||
bool bResult = true;
|
||||
foreach(char ch in strCharacters)
|
||||
foreach (char ch in strCharacters)
|
||||
{
|
||||
if(!Remove(ch)) bResult = false;
|
||||
if (!Remove(ch)) bResult = false;
|
||||
}
|
||||
|
||||
return bResult;
|
||||
@@ -292,9 +270,9 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
public bool RemoveIfAllExist(string strCharacters)
|
||||
{
|
||||
Debug.Assert(strCharacters != null);
|
||||
if(strCharacters == null) throw new ArgumentNullException("strCharacters");
|
||||
if (strCharacters == null) throw new ArgumentNullException("strCharacters");
|
||||
|
||||
if(!Contains(strCharacters))
|
||||
if (!Contains(strCharacters))
|
||||
return false;
|
||||
|
||||
return Remove(strCharacters);
|
||||
@@ -306,8 +284,8 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
/// <returns>String containing all character set characters.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach(char ch in m_vChars)
|
||||
StringBuilder sb = new StringBuilder(m_lChars.Count);
|
||||
foreach (char ch in m_lChars)
|
||||
sb.Append(ch);
|
||||
|
||||
return sb.ToString();
|
||||
@@ -320,32 +298,32 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_');
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_');
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_');
|
||||
sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_');
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_');
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_');
|
||||
sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_');
|
||||
sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_');
|
||||
sb.Append(RemoveIfAllExist(@" ") ? 's' : '_');
|
||||
sb.Append(RemoveIfAllExist("-") ? 'm' : '_');
|
||||
sb.Append(RemoveIfAllExist("_") ? 'u' : '_');
|
||||
sb.Append(RemoveIfAllExist(" ") ? 's' : '_');
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_');
|
||||
sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_');
|
||||
sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_');
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void UnpackCharRanges(string strRanges)
|
||||
{
|
||||
if(strRanges == null) { Debug.Assert(false); return; }
|
||||
if(strRanges.Length < 10) { Debug.Assert(false); return; }
|
||||
if (strRanges == null) { Debug.Assert(false); return; }
|
||||
if (strRanges.Length < 10) { Debug.Assert(false); return; }
|
||||
|
||||
if(strRanges[0] != '_') Add(PwCharSet.UpperCase);
|
||||
if(strRanges[1] != '_') Add(PwCharSet.LowerCase);
|
||||
if(strRanges[2] != '_') Add(PwCharSet.Digits);
|
||||
if(strRanges[3] != '_') Add(m_strSpecial);
|
||||
if(strRanges[4] != '_') Add(PwCharSet.Punctuation);
|
||||
if(strRanges[5] != '_') Add('-');
|
||||
if(strRanges[6] != '_') Add('_');
|
||||
if(strRanges[7] != '_') Add(' ');
|
||||
if(strRanges[8] != '_') Add(PwCharSet.Brackets);
|
||||
if(strRanges[9] != '_') Add(m_strHighAnsi);
|
||||
if (strRanges[0] != '_') Add(PwCharSet.UpperCase);
|
||||
if (strRanges[1] != '_') Add(PwCharSet.LowerCase);
|
||||
if (strRanges[2] != '_') Add(PwCharSet.Digits);
|
||||
if (strRanges[3] != '_') Add(PwCharSet.Special);
|
||||
if (strRanges[4] != '_') Add(PwCharSet.Punctuation);
|
||||
if (strRanges[5] != '_') Add('-');
|
||||
if (strRanges[6] != '_') Add('_');
|
||||
if (strRanges[7] != '_') Add(' ');
|
||||
if (strRanges[8] != '_') Add(PwCharSet.Brackets);
|
||||
if (strRanges[9] != '_') Add(PwCharSet.Latin1S);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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
|
||||
@@ -20,9 +20,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
#if !KeePassUAP
|
||||
using System.Security.Cryptography;
|
||||
#endif
|
||||
|
||||
using KeePassLib.Resources;
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
@@ -33,95 +37,78 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
Success = 0,
|
||||
Unknown = 1,
|
||||
TooFewCharacters = 2,
|
||||
UnknownAlgorithm = 3
|
||||
UnknownAlgorithm = 3,
|
||||
InvalidCharSet = 4,
|
||||
InvalidPattern = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for generating random passwords.
|
||||
/// Password generator.
|
||||
/// </summary>
|
||||
public static class PwGenerator
|
||||
{
|
||||
public static PwgError Generate(out ProtectedString psOut,
|
||||
PwProfile pwProfile, byte[] pbUserEntropy,
|
||||
CustomPwGeneratorPool pwAlgorithmPool)
|
||||
|
||||
private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
|
||||
out byte[] pbKey)
|
||||
{
|
||||
Debug.Assert(pwProfile != null);
|
||||
if (pwProfile == null) throw new ArgumentNullException("pwProfile");
|
||||
|
||||
CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy);
|
||||
PwgError e = PwgError.Unknown;
|
||||
|
||||
if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet)
|
||||
e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs);
|
||||
else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern)
|
||||
e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs);
|
||||
else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom)
|
||||
e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool);
|
||||
else { Debug.Assert(false); psOut = ProtectedString.Empty; }
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy)
|
||||
{
|
||||
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128);
|
||||
pbKey = CryptoRandom.Instance.GetRandomBytes(128);
|
||||
|
||||
// Mix in additional entropy
|
||||
Debug.Assert(pbKey.Length >= 64);
|
||||
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0))
|
||||
if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0))
|
||||
{
|
||||
using (SHA512Managed h = new SHA512Managed())
|
||||
{
|
||||
byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
|
||||
MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
|
||||
MemUtil.ZeroByteArray(pbHash);
|
||||
}
|
||||
}
|
||||
|
||||
return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
|
||||
}
|
||||
|
||||
internal static char GenerateCharacter(PwProfile pwProfile,
|
||||
PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)
|
||||
internal static char GenerateCharacter(PwCharSet pwCharSet,
|
||||
CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
if (pwCharSet.Size == 0) return char.MinValue;
|
||||
uint cc = pwCharSet.Size;
|
||||
if (cc == 0) return char.MinValue;
|
||||
|
||||
ulong uIndex = crsRandomSource.GetRandomUInt64();
|
||||
uIndex %= (ulong)pwCharSet.Size;
|
||||
|
||||
char ch = pwCharSet[(uint)uIndex];
|
||||
|
||||
if (pwProfile.NoRepeatingCharacters)
|
||||
pwCharSet.Remove(ch);
|
||||
|
||||
return ch;
|
||||
uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
|
||||
return pwCharSet[i];
|
||||
}
|
||||
|
||||
internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
|
||||
internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
|
||||
{
|
||||
pwCharSet.Remove(PwCharSet.Invalid);
|
||||
uint cc = pwCharSet.Size;
|
||||
for (uint i = 0; i < cc; ++i)
|
||||
{
|
||||
char ch = pwCharSet[i];
|
||||
if ((ch == char.MinValue) || (ch == '\t') || (ch == '\r') ||
|
||||
(ch == '\n') || char.IsSurrogate(ch))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
|
||||
|
||||
if (pwProfile.ExcludeCharacters.Length > 0)
|
||||
if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
|
||||
pwCharSet.Remove(pwProfile.ExcludeCharacters);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void ShufflePassword(char[] pPassword,
|
||||
CryptoRandomStream crsRandomSource)
|
||||
internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
Debug.Assert(pPassword != null); if (pPassword == null) return;
|
||||
Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return;
|
||||
if (v == null) { Debug.Assert(false); return; }
|
||||
if (crsRandomSource == null) { Debug.Assert(false); return; }
|
||||
|
||||
if (pPassword.Length <= 1) return; // Nothing to shuffle
|
||||
|
||||
for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect)
|
||||
for (int i = v.Length - 1; i >= 1; --i)
|
||||
{
|
||||
ulong uRandomIndex = crsRandomSource.GetRandomUInt64();
|
||||
uRandomIndex %= (ulong)(pPassword.Length - nSelect);
|
||||
int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
|
||||
|
||||
char chTemp = pPassword[nSelect];
|
||||
pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex];
|
||||
pPassword[nSelect + (int)uRandomIndex] = chTemp;
|
||||
char t = v[i];
|
||||
v[i] = v[j];
|
||||
v[j] = t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +122,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
|
||||
|
||||
string strID = pwProfile.CustomAlgorithmUuid;
|
||||
if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
|
||||
if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
|
||||
|
||||
byte[] pbUuid = Convert.FromBase64String(strID);
|
||||
PwUuid uuid = new PwUuid(pbUuid);
|
||||
@@ -148,5 +135,57 @@ namespace KeePassLib.Cryptography.PasswordGenerator
|
||||
psOut = pwd;
|
||||
return PwgError.Success;
|
||||
}
|
||||
|
||||
internal static string ErrorToString(PwgError e, bool bHeader)
|
||||
{
|
||||
if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
|
||||
if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
|
||||
|
||||
string str = KLRes.UnknownError;
|
||||
switch (e)
|
||||
{
|
||||
// case PwgError.Success:
|
||||
// break;
|
||||
|
||||
case PwgError.Unknown:
|
||||
break;
|
||||
|
||||
case PwgError.TooFewCharacters:
|
||||
str = KLRes.CharSetTooFewChars;
|
||||
break;
|
||||
|
||||
case PwgError.UnknownAlgorithm:
|
||||
str = KLRes.AlgorithmUnknown;
|
||||
break;
|
||||
|
||||
case PwgError.InvalidCharSet:
|
||||
str = KLRes.CharSetInvalid;
|
||||
break;
|
||||
|
||||
case PwgError.InvalidPattern:
|
||||
str = KLRes.PatternInvalid;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bHeader)
|
||||
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
internal static string ErrorToString(Exception ex, bool bHeader)
|
||||
{
|
||||
string str = ((ex == null) ? KLRes.UnknownError :
|
||||
StrUtil.FormatException(ex));
|
||||
|
||||
if (bHeader)
|
||||
str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using KeePassLib.Utility;
|
||||
|
||||
@@ -28,19 +28,19 @@ namespace KeePassLib.Cryptography
|
||||
{
|
||||
public static class PopularPasswords
|
||||
{
|
||||
private static Dictionary<int, Dictionary<string, bool>> m_dicts =
|
||||
new Dictionary<int, Dictionary<string, bool>>();
|
||||
private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts =
|
||||
new Dictionary<int, Dictionary<char[], bool>>();
|
||||
|
||||
internal static int MaxLength
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(m_dicts.Count > 0); // Should be initialized
|
||||
Debug.Assert(g_dicts.Count > 0); // Should be initialized
|
||||
|
||||
int iMaxLen = 0;
|
||||
foreach(int iLen in m_dicts.Keys)
|
||||
foreach (int iLen in g_dicts.Keys)
|
||||
{
|
||||
if(iLen > iMaxLen) iMaxLen = iLen;
|
||||
if (iLen > iMaxLen) iMaxLen = iLen;
|
||||
}
|
||||
|
||||
return iMaxLen;
|
||||
@@ -49,8 +49,8 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
internal static bool ContainsLength(int nLength)
|
||||
{
|
||||
Dictionary<string, bool> dDummy;
|
||||
return m_dicts.TryGetValue(nLength, out dDummy);
|
||||
Dictionary<char[], bool> dDummy;
|
||||
return g_dicts.TryGetValue(nLength, out dDummy);
|
||||
}
|
||||
|
||||
public static bool IsPopularPassword(char[] vPassword)
|
||||
@@ -61,74 +61,73 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize)
|
||||
{
|
||||
if(vPassword == null) throw new ArgumentNullException("vPassword");
|
||||
if(vPassword.Length == 0) { uDictSize = 0; return false; }
|
||||
if (vPassword == null) throw new ArgumentNullException("vPassword");
|
||||
if (vPassword.Length == 0) { uDictSize = 0; return false; }
|
||||
|
||||
string str = new string(vPassword);
|
||||
#if DEBUG
|
||||
Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch)));
|
||||
#endif
|
||||
|
||||
try { return IsPopularPasswordPriv(str, out uDictSize); }
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
try { return IsPopularPasswordPriv(vPassword, out uDictSize); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
|
||||
uDictSize = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsPopularPasswordPriv(string str, out ulong uDictSize)
|
||||
private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize)
|
||||
{
|
||||
Debug.Assert(m_dicts.Count > 0); // Should be initialized with data
|
||||
Debug.Assert(g_dicts.Count > 0); // Should be initialized with data
|
||||
|
||||
Dictionary<string, bool> d;
|
||||
if(!m_dicts.TryGetValue(str.Length, out d))
|
||||
Dictionary<char[], bool> d;
|
||||
if (!g_dicts.TryGetValue(vPassword.Length, out d))
|
||||
{
|
||||
uDictSize = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
uDictSize = (ulong)d.Count;
|
||||
return d.ContainsKey(str);
|
||||
return d.ContainsKey(vPassword);
|
||||
}
|
||||
|
||||
public static void Add(byte[] pbData, bool bGZipped)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(bGZipped)
|
||||
if (bGZipped)
|
||||
pbData = MemUtil.Decompress(pbData);
|
||||
|
||||
string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length);
|
||||
if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
|
||||
|
||||
if(!char.IsWhiteSpace(strData[strData.Length - 1]))
|
||||
strData += "\n";
|
||||
if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; }
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(int i = 0; i < strData.Length; ++i)
|
||||
for (int i = 0; i <= strData.Length; ++i)
|
||||
{
|
||||
char ch = strData[i];
|
||||
char ch = ((i == strData.Length) ? ' ' : strData[i]);
|
||||
|
||||
if(char.IsWhiteSpace(ch))
|
||||
if (char.IsWhiteSpace(ch))
|
||||
{
|
||||
int cc = sb.Length;
|
||||
if(cc > 0)
|
||||
if (cc > 0)
|
||||
{
|
||||
string strWord = sb.ToString();
|
||||
Debug.Assert(strWord.Length == cc);
|
||||
char[] vWord = new char[cc];
|
||||
sb.CopyTo(0, vWord, 0, cc);
|
||||
|
||||
Dictionary<string, bool> d;
|
||||
if(!m_dicts.TryGetValue(cc, out d))
|
||||
Dictionary<char[], bool> d;
|
||||
if (!g_dicts.TryGetValue(cc, out d))
|
||||
{
|
||||
d = new Dictionary<string, bool>();
|
||||
m_dicts[cc] = d;
|
||||
d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar);
|
||||
g_dicts[cc] = d;
|
||||
}
|
||||
|
||||
d[strWord] = true;
|
||||
d[vWord] = true;
|
||||
sb.Remove(0, cc);
|
||||
}
|
||||
}
|
||||
else sb.Append(char.ToLower(ch));
|
||||
}
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
catch (Exception) { Debug.Assert(false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using KeePassLib.Cryptography.PasswordGenerator;
|
||||
using KeePassLib.Utility;
|
||||
@@ -35,19 +35,19 @@ namespace KeePassLib.Cryptography
|
||||
{
|
||||
private static class PatternID
|
||||
{
|
||||
public const char LowerAlpha = 'L';
|
||||
public const char UpperAlpha = 'U';
|
||||
public const char Digit = 'D';
|
||||
public const char Special = 'S';
|
||||
public const char High = 'H';
|
||||
public const char Other = 'X';
|
||||
internal const char LowerAlpha = 'L';
|
||||
internal const char UpperAlpha = 'U';
|
||||
internal const char Digit = 'D';
|
||||
internal const char Special = 'S';
|
||||
internal const char Latin1S = 'H';
|
||||
internal const char Other = 'X';
|
||||
|
||||
public const char Dictionary = 'W';
|
||||
public const char Repetition = 'R';
|
||||
public const char Number = 'N';
|
||||
public const char DiffSeq = 'C';
|
||||
internal const char Dictionary = 'W';
|
||||
internal const char Repetition = 'R';
|
||||
internal const char Number = 'N';
|
||||
internal const char DiffSeq = 'C';
|
||||
|
||||
public const string All = "LUDSHXWRNC";
|
||||
internal const string All = "LUDSHXWRNC";
|
||||
}
|
||||
|
||||
// private static class CharDistrib
|
||||
@@ -84,8 +84,8 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
public QeCharType(char chTypeID, string strAlphabet, bool bIsConsecutive)
|
||||
{
|
||||
if(strAlphabet == null) throw new ArgumentNullException();
|
||||
if(strAlphabet.Length == 0) throw new ArgumentException();
|
||||
if (strAlphabet == null) throw new ArgumentNullException();
|
||||
if (strAlphabet.Length == 0) throw new ArgumentException();
|
||||
|
||||
m_chTypeID = chTypeID;
|
||||
m_strAlph = strAlphabet;
|
||||
@@ -101,7 +101,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
public QeCharType(char chTypeID, int nChars) // Catch-none set
|
||||
{
|
||||
if(nChars <= 0) throw new ArgumentOutOfRangeException();
|
||||
if (nChars <= 0) throw new ArgumentOutOfRangeException();
|
||||
|
||||
m_chTypeID = chTypeID;
|
||||
m_strAlph = string.Empty;
|
||||
@@ -114,7 +114,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
public bool Contains(char ch)
|
||||
{
|
||||
if(m_chLast != char.MinValue)
|
||||
if (m_chLast != char.MinValue)
|
||||
return ((ch >= m_chFirst) && (ch <= m_chLast));
|
||||
|
||||
Debug.Assert(m_strAlph.Length > 0); // Don't call for catch-none set
|
||||
@@ -125,7 +125,7 @@ namespace KeePassLib.Cryptography
|
||||
private sealed class EntropyEncoder
|
||||
{
|
||||
private readonly string m_strAlph;
|
||||
private Dictionary<char, ulong> m_dHisto = new Dictionary<char, ulong>();
|
||||
private readonly Dictionary<char, ulong> m_dHisto = new Dictionary<char, ulong>();
|
||||
private readonly ulong m_uBaseWeight;
|
||||
private readonly ulong m_uCharWeight;
|
||||
private readonly ulong m_uOccExclThreshold;
|
||||
@@ -133,8 +133,8 @@ namespace KeePassLib.Cryptography
|
||||
public EntropyEncoder(string strAlphabet, ulong uBaseWeight,
|
||||
ulong uCharWeight, ulong uOccExclThreshold)
|
||||
{
|
||||
if(strAlphabet == null) throw new ArgumentNullException();
|
||||
if(strAlphabet.Length == 0) throw new ArgumentException();
|
||||
if (strAlphabet == null) throw new ArgumentNullException();
|
||||
if (strAlphabet.Length == 0) throw new ArgumentException();
|
||||
|
||||
m_strAlph = strAlphabet;
|
||||
m_uBaseWeight = uBaseWeight;
|
||||
@@ -143,7 +143,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
#if DEBUG
|
||||
Dictionary<char, bool> d = new Dictionary<char, bool>();
|
||||
foreach(char ch in m_strAlph) { d[ch] = true; }
|
||||
foreach (char ch in m_strAlph) { d[ch] = true; }
|
||||
Debug.Assert(d.Count == m_strAlph.Length); // No duplicates
|
||||
#endif
|
||||
}
|
||||
@@ -166,18 +166,18 @@ namespace KeePassLib.Cryptography
|
||||
public double GetOutputSize()
|
||||
{
|
||||
ulong uTotalWeight = m_uBaseWeight * (ulong)m_strAlph.Length;
|
||||
foreach(ulong u in m_dHisto.Values)
|
||||
foreach (ulong u in m_dHisto.Values)
|
||||
{
|
||||
Debug.Assert(u >= 1);
|
||||
if(u > m_uOccExclThreshold)
|
||||
if (u > m_uOccExclThreshold)
|
||||
uTotalWeight += (u - m_uOccExclThreshold) * m_uCharWeight;
|
||||
}
|
||||
|
||||
double dSize = 0.0, dTotalWeight = (double)uTotalWeight;
|
||||
foreach(ulong u in m_dHisto.Values)
|
||||
foreach (ulong u in m_dHisto.Values)
|
||||
{
|
||||
ulong uWeight = m_uBaseWeight;
|
||||
if(u > m_uOccExclThreshold)
|
||||
if (u > m_uOccExclThreshold)
|
||||
uWeight += (u - m_uOccExclThreshold) * m_uCharWeight;
|
||||
|
||||
dSize -= (double)u * Log2((double)uWeight / dTotalWeight);
|
||||
@@ -189,7 +189,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
private sealed class MultiEntropyEncoder
|
||||
{
|
||||
private Dictionary<char, EntropyEncoder> m_dEncs =
|
||||
private readonly Dictionary<char, EntropyEncoder> m_dEncs =
|
||||
new Dictionary<char, EntropyEncoder>();
|
||||
|
||||
public MultiEntropyEncoder()
|
||||
@@ -198,7 +198,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
public void AddEncoder(char chTypeID, EntropyEncoder ec)
|
||||
{
|
||||
if(ec == null) { Debug.Assert(false); return; }
|
||||
if (ec == null) { Debug.Assert(false); return; }
|
||||
|
||||
Debug.Assert(!m_dEncs.ContainsKey(chTypeID));
|
||||
m_dEncs[chTypeID] = ec;
|
||||
@@ -206,13 +206,13 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach(EntropyEncoder ec in m_dEncs.Values) { ec.Reset(); }
|
||||
foreach (EntropyEncoder ec in m_dEncs.Values) { ec.Reset(); }
|
||||
}
|
||||
|
||||
public bool Write(char chTypeID, char chData)
|
||||
{
|
||||
EntropyEncoder ec;
|
||||
if(!m_dEncs.TryGetValue(chTypeID, out ec))
|
||||
if (!m_dEncs.TryGetValue(chTypeID, out ec))
|
||||
return false;
|
||||
|
||||
ec.Write(chData);
|
||||
@@ -223,7 +223,7 @@ namespace KeePassLib.Cryptography
|
||||
{
|
||||
double d = 0.0;
|
||||
|
||||
foreach(EntropyEncoder ec in m_dEncs.Values)
|
||||
foreach (EntropyEncoder ec in m_dEncs.Values)
|
||||
{
|
||||
d += ec.GetOutputSize();
|
||||
}
|
||||
@@ -281,36 +281,31 @@ namespace KeePassLib.Cryptography
|
||||
}
|
||||
}
|
||||
|
||||
private static object m_objSyncInit = new object();
|
||||
private static readonly object m_objSyncInit = new object();
|
||||
private static List<QeCharType> m_lCharTypes = null;
|
||||
|
||||
private static void EnsureInitialized()
|
||||
{
|
||||
lock(m_objSyncInit)
|
||||
lock (m_objSyncInit)
|
||||
{
|
||||
if(m_lCharTypes == null)
|
||||
if (m_lCharTypes == null)
|
||||
{
|
||||
string strSpecial = PwCharSet.PrintableAsciiSpecial;
|
||||
if(strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); }
|
||||
else strSpecial = strSpecial + " ";
|
||||
if (strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); }
|
||||
else strSpecial += " ";
|
||||
|
||||
int nSp = strSpecial.Length;
|
||||
int nHi = PwCharSet.HighAnsiChars.Length;
|
||||
int nL1S = PwCharSet.Latin1S.Length;
|
||||
|
||||
m_lCharTypes = new List<QeCharType>();
|
||||
|
||||
m_lCharTypes.Add(new QeCharType(PatternID.LowerAlpha,
|
||||
PwCharSet.LowerCase, true));
|
||||
m_lCharTypes.Add(new QeCharType(PatternID.UpperAlpha,
|
||||
PwCharSet.UpperCase, true));
|
||||
m_lCharTypes.Add(new QeCharType(PatternID.Digit,
|
||||
PwCharSet.Digits, true));
|
||||
m_lCharTypes.Add(new QeCharType(PatternID.Special,
|
||||
strSpecial, false));
|
||||
m_lCharTypes.Add(new QeCharType(PatternID.High,
|
||||
PwCharSet.HighAnsiChars, false));
|
||||
m_lCharTypes.Add(new QeCharType(PatternID.Other,
|
||||
0x10000 - (2 * 26) - 10 - nSp - nHi));
|
||||
m_lCharTypes = new List<QeCharType>()
|
||||
{
|
||||
new QeCharType(PatternID.LowerAlpha, PwCharSet.LowerCase, true),
|
||||
new QeCharType(PatternID.UpperAlpha, PwCharSet.UpperCase, true),
|
||||
new QeCharType(PatternID.Digit, PwCharSet.Digits, true),
|
||||
new QeCharType(PatternID.Special, strSpecial, false),
|
||||
new QeCharType(PatternID.Latin1S, PwCharSet.Latin1S, false),
|
||||
new QeCharType(PatternID.Other, 0x10000 - (2 * 26) - 10 - nSp - nL1S)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,37 +313,37 @@ namespace KeePassLib.Cryptography
|
||||
/// <summary>
|
||||
/// Estimate the quality of a password.
|
||||
/// </summary>
|
||||
/// <param name="vPasswordChars">Password to check.</param>
|
||||
/// <param name="vPassword">Password to check.</param>
|
||||
/// <returns>Estimated bit-strength of the password.</returns>
|
||||
public static uint EstimatePasswordBits(char[] vPasswordChars)
|
||||
public static uint EstimatePasswordBits(char[] vPassword)
|
||||
{
|
||||
if(vPasswordChars == null) { Debug.Assert(false); return 0; }
|
||||
if(vPasswordChars.Length == 0) return 0;
|
||||
if (vPassword == null) { Debug.Assert(false); return 0; }
|
||||
if (vPassword.Length == 0) return 0;
|
||||
|
||||
EnsureInitialized();
|
||||
|
||||
int n = vPasswordChars.Length;
|
||||
int n = vPassword.Length;
|
||||
List<QePatternInstance>[] vPatterns = new List<QePatternInstance>[n];
|
||||
for(int i = 0; i < n; ++i)
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
vPatterns[i] = new List<QePatternInstance>();
|
||||
|
||||
QePatternInstance piChar = new QePatternInstance(i, 1,
|
||||
GetCharType(vPasswordChars[i]));
|
||||
GetCharType(vPassword[i]));
|
||||
vPatterns[i].Add(piChar);
|
||||
}
|
||||
|
||||
FindRepetitions(vPasswordChars, vPatterns);
|
||||
FindNumbers(vPasswordChars, vPatterns);
|
||||
FindDiffSeqs(vPasswordChars, vPatterns);
|
||||
FindPopularPasswords(vPasswordChars, vPatterns);
|
||||
FindRepetitions(vPassword, vPatterns);
|
||||
FindNumbers(vPassword, vPatterns);
|
||||
FindDiffSeqs(vPassword, vPatterns);
|
||||
FindPopularPasswords(vPassword, vPatterns);
|
||||
|
||||
// Encoders must not be static, because the entropy estimation
|
||||
// may run concurrently in multiple threads and the encoders are
|
||||
// not read-only
|
||||
EntropyEncoder ecPattern = new EntropyEncoder(PatternID.All, 0, 1, 0);
|
||||
MultiEntropyEncoder mcData = new MultiEntropyEncoder();
|
||||
for(int i = 0; i < (m_lCharTypes.Count - 1); ++i)
|
||||
for (int i = 0; i < (m_lCharTypes.Count - 1); ++i)
|
||||
{
|
||||
// Let m be the alphabet size. In order to ensure that two same
|
||||
// characters cost at least as much as a single character, for
|
||||
@@ -371,25 +366,25 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
Stack<QePathState> sRec = new Stack<QePathState>();
|
||||
sRec.Push(new QePathState(0, new List<QePatternInstance>()));
|
||||
while(sRec.Count > 0)
|
||||
while (sRec.Count > 0)
|
||||
{
|
||||
int tDiff = Environment.TickCount - tStart;
|
||||
if(tDiff > 500) break;
|
||||
if (tDiff > 500) break;
|
||||
|
||||
QePathState s = sRec.Pop();
|
||||
|
||||
if(s.Position >= n)
|
||||
if (s.Position >= n)
|
||||
{
|
||||
Debug.Assert(s.Position == n);
|
||||
|
||||
double dblCost = ComputePathCost(s.Path, vPasswordChars,
|
||||
double dblCost = ComputePathCost(s.Path, vPassword,
|
||||
ecPattern, mcData);
|
||||
if(dblCost < dblMinCost) dblMinCost = dblCost;
|
||||
if (dblCost < dblMinCost) dblMinCost = dblCost;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<QePatternInstance> lSubs = vPatterns[s.Position];
|
||||
for(int i = lSubs.Count - 1; i >= 0; --i)
|
||||
for (int i = lSubs.Count - 1; i >= 0; --i)
|
||||
{
|
||||
QePatternInstance pi = lSubs[i];
|
||||
Debug.Assert(pi.Position == s.Position);
|
||||
@@ -418,13 +413,14 @@ namespace KeePassLib.Cryptography
|
||||
/// <returns>Estimated bit-strength of the password.</returns>
|
||||
public static uint EstimatePasswordBits(byte[] pbUnprotectedUtf8)
|
||||
{
|
||||
if(pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; }
|
||||
if (pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; }
|
||||
|
||||
char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8);
|
||||
uint uResult = EstimatePasswordBits(vChars);
|
||||
MemUtil.ZeroArray<char>(vChars);
|
||||
char[] v = StrUtil.Utf8.GetChars(pbUnprotectedUtf8);
|
||||
uint r;
|
||||
try { r = EstimatePasswordBits(v); }
|
||||
finally { MemUtil.ZeroArray<char>(v); }
|
||||
|
||||
return uResult;
|
||||
return r;
|
||||
}
|
||||
|
||||
private static QeCharType GetCharType(char ch)
|
||||
@@ -432,9 +428,9 @@ namespace KeePassLib.Cryptography
|
||||
int nTypes = m_lCharTypes.Count;
|
||||
Debug.Assert((nTypes > 0) && (m_lCharTypes[nTypes - 1].CharCount > 256));
|
||||
|
||||
for(int i = 0; i < (nTypes - 1); ++i)
|
||||
for (int i = 0; i < (nTypes - 1); ++i)
|
||||
{
|
||||
if(m_lCharTypes[i].Contains(ch))
|
||||
if (m_lCharTypes[i].Contains(ch))
|
||||
return m_lCharTypes[i];
|
||||
}
|
||||
|
||||
@@ -445,19 +441,19 @@ namespace KeePassLib.Cryptography
|
||||
char[] vPassword, EntropyEncoder ecPattern, MultiEntropyEncoder mcData)
|
||||
{
|
||||
ecPattern.Reset();
|
||||
for(int i = 0; i < l.Count; ++i)
|
||||
for (int i = 0; i < l.Count; ++i)
|
||||
ecPattern.Write(l[i].PatternID);
|
||||
double dblPatternCost = ecPattern.GetOutputSize();
|
||||
|
||||
mcData.Reset();
|
||||
double dblDataCost = 0.0;
|
||||
foreach(QePatternInstance pi in l)
|
||||
foreach (QePatternInstance pi in l)
|
||||
{
|
||||
QeCharType tChar = pi.SingleCharType;
|
||||
if(tChar != null)
|
||||
if (tChar != null)
|
||||
{
|
||||
char ch = vPassword[pi.Position];
|
||||
if(!mcData.Write(tChar.TypeID, ch))
|
||||
if (!mcData.Write(tChar.TypeID, ch))
|
||||
dblDataCost += pi.Cost;
|
||||
}
|
||||
else dblDataCost += pi.Cost;
|
||||
@@ -474,7 +470,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
char[] vLower = new char[n];
|
||||
char[] vLeet = new char[n];
|
||||
for(int i = 0; i < n; ++i)
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
char ch = vPassword[i];
|
||||
|
||||
@@ -482,27 +478,27 @@ namespace KeePassLib.Cryptography
|
||||
vLeet[i] = char.ToLower(DecodeLeetChar(ch));
|
||||
}
|
||||
|
||||
char chErased = default(char);
|
||||
char chErased = default(char); // The value that Array.Clear uses
|
||||
Debug.Assert(chErased == char.MinValue);
|
||||
|
||||
int nMaxLen = Math.Min(n, PopularPasswords.MaxLength);
|
||||
for(int nSubLen = nMaxLen; nSubLen >= 3; --nSubLen)
|
||||
for (int nSubLen = nMaxLen; nSubLen >= 3; --nSubLen)
|
||||
{
|
||||
if(!PopularPasswords.ContainsLength(nSubLen)) continue;
|
||||
if (!PopularPasswords.ContainsLength(nSubLen)) continue;
|
||||
|
||||
char[] vSub = new char[nSubLen];
|
||||
|
||||
for(int i = 0; i <= (n - nSubLen); ++i)
|
||||
for (int i = 0; i <= (n - nSubLen); ++i)
|
||||
{
|
||||
if(Array.IndexOf<char>(vLower, chErased, i, nSubLen) >= 0)
|
||||
if (Array.IndexOf<char>(vLower, chErased, i, nSubLen) >= 0)
|
||||
continue;
|
||||
|
||||
Array.Copy(vLower, i, vSub, 0, nSubLen);
|
||||
if(!EvalAddPopularPasswordPattern(vPatterns, vPassword,
|
||||
if (!EvalAddPopularPasswordPattern(vPatterns, vPassword,
|
||||
i, vSub, 0.0))
|
||||
{
|
||||
Array.Copy(vLeet, i, vSub, 0, nSubLen);
|
||||
if(EvalAddPopularPasswordPattern(vPatterns, vPassword,
|
||||
if (EvalAddPopularPasswordPattern(vPatterns, vPassword,
|
||||
i, vSub, 1.5))
|
||||
{
|
||||
Array.Clear(vLower, i, nSubLen); // Not vLeet
|
||||
@@ -515,14 +511,19 @@ namespace KeePassLib.Cryptography
|
||||
Debug.Assert(vLower[i] == chErased);
|
||||
}
|
||||
}
|
||||
|
||||
MemUtil.ZeroArray<char>(vSub);
|
||||
}
|
||||
|
||||
MemUtil.ZeroArray<char>(vLower);
|
||||
MemUtil.ZeroArray<char>(vLeet);
|
||||
}
|
||||
|
||||
private static bool EvalAddPopularPasswordPattern(List<QePatternInstance>[] vPatterns,
|
||||
char[] vPassword, int i, char[] vSub, double dblCostPerMod)
|
||||
{
|
||||
ulong uDictSize;
|
||||
if(!PopularPasswords.IsPopularPassword(vSub, out uDictSize))
|
||||
if (!PopularPasswords.IsPopularPassword(vSub, out uDictSize))
|
||||
return false;
|
||||
|
||||
int n = vSub.Length;
|
||||
@@ -532,9 +533,9 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
// dblCost += log2(n binom d)
|
||||
int k = Math.Min(d, n - d);
|
||||
for(int j = n; j > (n - k); --j)
|
||||
for (int j = n; j > (n - k); --j)
|
||||
dblCost += Log2(j);
|
||||
for(int j = k; j >= 2; --j)
|
||||
for (int j = k; j >= 2; --j)
|
||||
dblCost -= Log2(j);
|
||||
|
||||
dblCost += dblCostPerMod * (double)d;
|
||||
@@ -546,19 +547,19 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
private static char DecodeLeetChar(char chLeet)
|
||||
{
|
||||
if((chLeet >= '\u00C0') && (chLeet <= '\u00C6')) return 'a';
|
||||
if((chLeet >= '\u00C8') && (chLeet <= '\u00CB')) return 'e';
|
||||
if((chLeet >= '\u00CC') && (chLeet <= '\u00CF')) return 'i';
|
||||
if((chLeet >= '\u00D2') && (chLeet <= '\u00D6')) return 'o';
|
||||
if((chLeet >= '\u00D9') && (chLeet <= '\u00DC')) return 'u';
|
||||
if((chLeet >= '\u00E0') && (chLeet <= '\u00E6')) return 'a';
|
||||
if((chLeet >= '\u00E8') && (chLeet <= '\u00EB')) return 'e';
|
||||
if((chLeet >= '\u00EC') && (chLeet <= '\u00EF')) return 'i';
|
||||
if((chLeet >= '\u00F2') && (chLeet <= '\u00F6')) return 'o';
|
||||
if((chLeet >= '\u00F9') && (chLeet <= '\u00FC')) return 'u';
|
||||
if ((chLeet >= '\u00C0') && (chLeet <= '\u00C6')) return 'a';
|
||||
if ((chLeet >= '\u00C8') && (chLeet <= '\u00CB')) return 'e';
|
||||
if ((chLeet >= '\u00CC') && (chLeet <= '\u00CF')) return 'i';
|
||||
if ((chLeet >= '\u00D2') && (chLeet <= '\u00D6')) return 'o';
|
||||
if ((chLeet >= '\u00D9') && (chLeet <= '\u00DC')) return 'u';
|
||||
if ((chLeet >= '\u00E0') && (chLeet <= '\u00E6')) return 'a';
|
||||
if ((chLeet >= '\u00E8') && (chLeet <= '\u00EB')) return 'e';
|
||||
if ((chLeet >= '\u00EC') && (chLeet <= '\u00EF')) return 'i';
|
||||
if ((chLeet >= '\u00F2') && (chLeet <= '\u00F6')) return 'o';
|
||||
if ((chLeet >= '\u00F9') && (chLeet <= '\u00FC')) return 'u';
|
||||
|
||||
char ch;
|
||||
switch(chLeet)
|
||||
switch (chLeet)
|
||||
{
|
||||
case '4':
|
||||
case '@':
|
||||
@@ -621,9 +622,9 @@ namespace KeePassLib.Cryptography
|
||||
char[] v2, int iOffset2, int nLength)
|
||||
{
|
||||
int nDist = 0;
|
||||
for(int i = 0; i < nLength; ++i)
|
||||
for (int i = 0; i < nLength; ++i)
|
||||
{
|
||||
if(v1[iOffset1 + i] != v2[iOffset2 + i]) ++nDist;
|
||||
if (v1[iOffset1 + i] != v2[iOffset2 + i]) ++nDist;
|
||||
}
|
||||
|
||||
return nDist;
|
||||
@@ -637,15 +638,15 @@ namespace KeePassLib.Cryptography
|
||||
Array.Copy(vPassword, v, n);
|
||||
|
||||
char chErased = char.MaxValue;
|
||||
for(int m = (n / 2); m >= 3; --m)
|
||||
for (int m = (n / 2); m >= 3; --m)
|
||||
{
|
||||
for(int x1 = 0; x1 <= (n - (2 * m)); ++x1)
|
||||
for (int x1 = 0; x1 <= (n - (2 * m)); ++x1)
|
||||
{
|
||||
bool bFoundRep = false;
|
||||
|
||||
for(int x2 = (x1 + m); x2 <= (n - m); ++x2)
|
||||
for (int x2 = (x1 + m); x2 <= (n - m); ++x2)
|
||||
{
|
||||
if(PartsEqual(v, x1, x2, m))
|
||||
if (PartsEqual(v, x1, x2, m))
|
||||
{
|
||||
double dblCost = Log2(x1 + 1) + Log2(m);
|
||||
vPatterns[x2].Add(new QePatternInstance(x2, m,
|
||||
@@ -656,16 +657,18 @@ namespace KeePassLib.Cryptography
|
||||
}
|
||||
}
|
||||
|
||||
if(bFoundRep) ErasePart(v, x1, m, ref chErased);
|
||||
if (bFoundRep) ErasePart(v, x1, m, ref chErased);
|
||||
}
|
||||
}
|
||||
|
||||
MemUtil.ZeroArray<char>(v);
|
||||
}
|
||||
|
||||
private static bool PartsEqual(char[] v, int x1, int x2, int nLength)
|
||||
{
|
||||
for(int i = 0; i < nLength; ++i)
|
||||
for (int i = 0; i < nLength; ++i)
|
||||
{
|
||||
if(v[x1 + i] != v[x2 + i]) return false;
|
||||
if (v[x1 + i] != v[x2 + i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -673,7 +676,7 @@ namespace KeePassLib.Cryptography
|
||||
|
||||
private static void ErasePart(char[] v, int i, int n, ref char chErased)
|
||||
{
|
||||
for(int j = 0; j < n; ++j)
|
||||
for (int j = 0; j < n; ++j)
|
||||
{
|
||||
v[i + j] = chErased;
|
||||
--chErased;
|
||||
@@ -685,33 +688,35 @@ namespace KeePassLib.Cryptography
|
||||
{
|
||||
int n = vPassword.Length;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(int i = 0; i < n; ++i)
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
char ch = vPassword[i];
|
||||
if((ch >= '0') && (ch <= '9')) sb.Append(ch);
|
||||
if ((ch >= '0') && (ch <= '9')) sb.Append(ch);
|
||||
else
|
||||
{
|
||||
AddNumberPattern(vPatterns, sb.ToString(), i - sb.Length);
|
||||
AddNumberPattern(vPatterns, sb, i - sb.Length);
|
||||
sb.Remove(0, sb.Length);
|
||||
}
|
||||
}
|
||||
AddNumberPattern(vPatterns, sb.ToString(), n - sb.Length);
|
||||
AddNumberPattern(vPatterns, sb, n - sb.Length);
|
||||
}
|
||||
|
||||
private static void AddNumberPattern(List<QePatternInstance>[] vPatterns,
|
||||
string strNumber, int i)
|
||||
StringBuilder sb, int i)
|
||||
{
|
||||
if(strNumber.Length <= 2) return;
|
||||
if (sb.Length <= 2) return;
|
||||
string strNumber = sb.ToString();
|
||||
|
||||
int nZeros = 0;
|
||||
for(int j = 0; j < strNumber.Length; ++j)
|
||||
for (int j = 0; j < strNumber.Length; ++j)
|
||||
{
|
||||
if(strNumber[j] != '0') break;
|
||||
if (strNumber[j] != '0') break;
|
||||
++nZeros;
|
||||
}
|
||||
|
||||
double dblCost = Log2(nZeros + 1);
|
||||
if(nZeros < strNumber.Length)
|
||||
if (nZeros < strNumber.Length)
|
||||
{
|
||||
string strNonZero = strNumber.Substring(nZeros);
|
||||
|
||||
@@ -720,7 +725,7 @@ namespace KeePassLib.Cryptography
|
||||
catch(Exception) { Debug.Assert(false); return; }
|
||||
#else
|
||||
double d;
|
||||
if(double.TryParse(strNonZero, out d))
|
||||
if (double.TryParse(strNonZero, out d))
|
||||
dblCost += Log2(d);
|
||||
else { Debug.Assert(false); return; }
|
||||
#endif
|
||||
@@ -733,17 +738,18 @@ namespace KeePassLib.Cryptography
|
||||
private static void FindDiffSeqs(char[] vPassword,
|
||||
List<QePatternInstance>[] vPatterns)
|
||||
{
|
||||
int d = int.MinValue, p = 0;
|
||||
string str = new string(vPassword) + new string(char.MaxValue, 1);
|
||||
int n = vPassword.Length;
|
||||
int d = int.MaxValue, p = 0;
|
||||
|
||||
for(int i = 1; i < str.Length; ++i)
|
||||
for (int i = 1; i <= n; ++i)
|
||||
{
|
||||
int dCur = (int)str[i] - (int)str[i - 1];
|
||||
if(dCur != d)
|
||||
int dCur = ((i == n) ? int.MinValue :
|
||||
((int)vPassword[i] - (int)vPassword[i - 1]));
|
||||
if (dCur != d)
|
||||
{
|
||||
if((i - p) >= 3) // At least 3 chars involved
|
||||
if ((i - p) >= 3) // At least 3 chars involved
|
||||
{
|
||||
QeCharType ct = GetCharType(str[p]);
|
||||
QeCharType ct = GetCharType(vPassword[p]);
|
||||
double dblCost = ct.CharSize + Log2(i - p - 1);
|
||||
|
||||
vPatterns[p].Add(new QePatternInstance(p,
|
||||
|
@@ -46,4 +46,12 @@ namespace KeePassLib.Delegates
|
||||
public delegate void VoidDelegate();
|
||||
|
||||
public delegate string StrPwEntryDelegate(string str, PwEntry pe);
|
||||
|
||||
public delegate TResult GFunc<TResult>();
|
||||
public delegate TResult GFunc<T, TResult>(T o);
|
||||
public delegate TResult GFunc<T1, T2, TResult>(T1 o1, T2 o2);
|
||||
public delegate TResult GFunc<T1, T2, T3, TResult>(T1 o1, T2 o2, T3 o3);
|
||||
public delegate TResult GFunc<T1, T2, T3, T4, TResult>(T1 o1, T2 o2, T3 o3, T4 o4);
|
||||
public delegate TResult GFunc<T1, T2, T3, T4, T5, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5);
|
||||
public delegate TResult GFunc<T1, T2, T3, T4, T5, T6, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -116,12 +116,23 @@ namespace keepass2android
|
||||
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.SetAction(Intent.ActionSend);
|
||||
sendIntent.PutExtra(Intent.ExtraText, File.ReadAllText(LogFilename));
|
||||
string logText = File.ReadAllText(LogFilename);
|
||||
|
||||
sendIntent.PutExtra(Intent.ExtraText, logText);
|
||||
sendIntent.PutExtra(Intent.ExtraEmail, "crocoapps@gmail.com");
|
||||
sendIntent.PutExtra(Intent.ExtraSubject, "Keepass2Android log");
|
||||
sendIntent.SetType("text/plain");
|
||||
try
|
||||
{
|
||||
ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to..."));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Toast.MakeText(ctx, $"Error sending log of length {logText.Length} bytes: " + e.Message, ToastLength.Long)?.Show();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void LogTask(object task, string activityName)
|
||||
{
|
||||
|
@@ -83,6 +83,7 @@ namespace KeePassLib.Serialization
|
||||
if (m_bUsedOnce)
|
||||
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
|
||||
m_bUsedOnce = true;
|
||||
Kp2aLog.Log("Starting to load KDBX file...");
|
||||
|
||||
#if KDBX_BENCHMARK
|
||||
Stopwatch swTime = Stopwatch.StartNew();
|
||||
@@ -257,6 +258,7 @@ namespace KeePassLib.Serialization
|
||||
MessageService.ShowInfo("Loading KDBX took " +
|
||||
swTime.ElapsedMilliseconds.ToString() + " ms.");
|
||||
#endif
|
||||
Kp2aLog.Log("Finished loading KDBX file.");
|
||||
}
|
||||
|
||||
private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing)
|
||||
|
@@ -95,7 +95,7 @@ namespace Kp2aAutofillParserTest
|
||||
StructureParserBase<TestInputField> parser =
|
||||
new StructureParserBase<TestInputField>(new TestLogger(), new TestDalSourceTrustAll());
|
||||
|
||||
var result = parser.ParseForFill(false, autofillView);
|
||||
var result = parser.ParseForFill(autofillView);
|
||||
if (expectedPackageName != null)
|
||||
Assert.Equal(expectedPackageName, result.PackageName);
|
||||
if (expectedWebDomain != null)
|
||||
|
@@ -58,7 +58,8 @@
|
||||
"IsFocused": false,
|
||||
"InputType": 97,
|
||||
"HtmlInfoTag": null,
|
||||
"HtmlInfoTypeAttribute": null
|
||||
"HtmlInfoTypeAttribute": null,
|
||||
"ExpectedAssignedHints": [ "username" ]
|
||||
},
|
||||
{
|
||||
"IdEntry": "password_text_input_layout",
|
||||
@@ -81,6 +82,7 @@
|
||||
"InputType": 129,
|
||||
"HtmlInfoTag": null,
|
||||
"HtmlInfoTypeAttribute": null,
|
||||
"ExpectedAssignedHints": [ "password" ]
|
||||
|
||||
},
|
||||
{
|
||||
|
@@ -445,6 +445,9 @@ namespace Kp2aAutofillParser
|
||||
|
||||
public static string ToCanonicalHint(string hint)
|
||||
{
|
||||
//avoid crash when looking up a null key
|
||||
if (hint == null)
|
||||
return "";
|
||||
string canonicalHint;
|
||||
if (!hintToCanonicalReplacement.TryGetValue(hint, out canonicalHint))
|
||||
canonicalHint = hint;
|
||||
@@ -473,8 +476,16 @@ namespace Kp2aAutofillParser
|
||||
|
||||
foreach (var field in autofillFields.HintMap.Values.Distinct())
|
||||
{
|
||||
if (field == null || field.AutofillHints == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var hint in field.AutofillHints)
|
||||
{
|
||||
if (hint == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (GetPartitionIndex(hint) == partitionIndex)
|
||||
{
|
||||
filteredCollection.Add(field);
|
||||
@@ -790,14 +801,14 @@ namespace Kp2aAutofillParser
|
||||
}
|
||||
}
|
||||
|
||||
public AutofillTargetId ParseForFill(bool isManual, AutofillView<FieldT> autofillView)
|
||||
public AutofillTargetId ParseForFill(AutofillView<FieldT> autofillView)
|
||||
{
|
||||
return Parse(true, isManual, autofillView);
|
||||
return Parse(true, autofillView);
|
||||
}
|
||||
|
||||
public AutofillTargetId ParseForSave(AutofillView<FieldT> autofillView)
|
||||
{
|
||||
return Parse(false, true, autofillView);
|
||||
return Parse(false, autofillView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -805,8 +816,7 @@ namespace Kp2aAutofillParser
|
||||
/// </summary>
|
||||
/// <returns>The parse.</returns>
|
||||
/// <param name="forFill">If set to <c>true</c> for fill.</param>
|
||||
/// <param name="isManualRequest"></param>
|
||||
protected virtual AutofillTargetId Parse(bool forFill, bool isManualRequest, AutofillView<FieldT> autofillView)
|
||||
protected virtual AutofillTargetId Parse(bool forFill, AutofillView<FieldT> autofillView)
|
||||
{
|
||||
AutofillTargetId result = new AutofillTargetId()
|
||||
{
|
||||
@@ -841,6 +851,7 @@ namespace Kp2aAutofillParser
|
||||
continue;
|
||||
if (viewHints.Where(h => h != null).Select(AutofillHintsHelper.ToCanonicalHint).Intersect(_autofillHintsForLogin).Any())
|
||||
{
|
||||
|
||||
AddFieldToHintMap(viewNode, viewHints.Where(h => h != null).Select(AutofillHintsHelper.ToCanonicalHint).ToHashSet().ToArray());
|
||||
}
|
||||
|
||||
@@ -872,8 +883,9 @@ namespace Kp2aAutofillParser
|
||||
|
||||
}
|
||||
|
||||
//for "heuristic determination" we demand that one of the filled fields is focused:
|
||||
if (passwordFields.Concat(usernameFields).Any(f => f.IsFocused))
|
||||
//for "heuristic determination" we demand that there is a password field or one of the username fields is focused:
|
||||
//Note that "IsFocused" might be false even when tapping the field. It might require long-press to autofill.
|
||||
if (passwordFields.Any() || usernameFields.Any(f => f.IsFocused))
|
||||
{
|
||||
foreach (var uf in usernameFields)
|
||||
AddFieldToHintMap(uf, new string[] { AutofillHintsHelper.AutofillHintUsername });
|
||||
|
@@ -29,6 +29,14 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
|
||||
|
||||
public enum MessageSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data
|
||||
/// </summary>
|
||||
@@ -102,6 +110,9 @@ namespace keepass2android
|
||||
Context ctx,
|
||||
string messageSuffix = "");
|
||||
|
||||
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
|
||||
void ShowMessage(Context ctx, string text, MessageSeverity severity);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Handler object which can run tasks on the UI thread
|
||||
/// </summary>
|
||||
@@ -129,6 +140,10 @@ namespace keepass2android
|
||||
|
||||
|
||||
#endif
|
||||
int WebDavChunkedUploadSize
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Java.IO;
|
||||
|
||||
using KeePass.Util;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using File = System.IO.File;
|
||||
@@ -121,7 +121,7 @@ namespace keepass2android.Io
|
||||
var response = ex.Response as HttpWebResponse;
|
||||
if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound))
|
||||
{
|
||||
throw new FileNotFoundException(ex.Message, ioc.Path, ex);
|
||||
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(ex), ioc.Path, ex);
|
||||
}
|
||||
if (ex.Status == WebExceptionStatus.TrustFailure)
|
||||
{
|
||||
|
@@ -15,6 +15,8 @@ namespace keepass2android.Io
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
|
||||
}
|
||||
|
||||
public partial class DropboxAppFolderFileStorage: JavaFileStorage
|
||||
@@ -29,6 +31,7 @@ namespace keepass2android.Io
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,13 +0,0 @@
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
public partial class DropboxFileStorage
|
||||
{
|
||||
private const string AppKey = "dummy";
|
||||
private const string AppSecret = "dummy";
|
||||
}
|
||||
public partial class DropboxAppFolderFileStorage
|
||||
{
|
||||
private const string AppKey = "dummy";
|
||||
private const string AppSecret = "dummy";
|
||||
}
|
||||
}
|
27
src/Kp2aBusinessLogic/Io/GenerateSecrets.targets
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project>
|
||||
<Target Name="GenerateDropboxSecrets" BeforeTargets="BeforeCompile"
|
||||
Inputs="@(DropboxSecretLines)"
|
||||
Outputs="DropboxFileStorage.g.cs">
|
||||
|
||||
<WriteLinesToFile
|
||||
File="Io/DropboxFileStorage.g.cs"
|
||||
Lines="@(DropboxSecretLines->'%(Text)')"
|
||||
Overwrite="true"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<DropboxSecretLines Include="GeneratedDropboxSecrets">
|
||||
<Text>namespace keepass2android.Io {
|
||||
public partial class DropboxFileStorage {
|
||||
private const string AppKey = "$(DropboxAppKey)";
|
||||
private const string AppSecret = "$(DropboxAppSecret)";
|
||||
}
|
||||
public partial class DropboxAppFolderFileStorage {
|
||||
private const string AppKey = "$(DropboxAppFolderAppKey)";
|
||||
private const string AppSecret = "$(DropboxAppFolderAppSecret)";
|
||||
}
|
||||
}</Text>
|
||||
</DropboxSecretLines>
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -13,6 +13,7 @@ using Keepass2android.Javafilestorage;
|
||||
#endif
|
||||
using Exception = System.Exception;
|
||||
using FileNotFoundException = Java.IO.FileNotFoundException;
|
||||
using KeePass.Util;
|
||||
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
@@ -122,7 +123,7 @@ namespace keepass2android.Io
|
||||
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
public virtual IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this);
|
||||
}
|
||||
@@ -195,7 +196,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
@@ -214,7 +215,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
@@ -244,7 +245,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
||||
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
|
@@ -8,6 +8,7 @@ using Android.Content;
|
||||
using Android.OS;
|
||||
using FluentFTP;
|
||||
using FluentFTP.Exceptions;
|
||||
using KeePass.Util;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
@@ -127,7 +128,7 @@ namespace keepass2android.Io
|
||||
var ftpEx = (FtpCommandException) exception;
|
||||
|
||||
if (ftpEx.CompletionCode == "550")
|
||||
throw new FileNotFoundException(exception.Message, exception);
|
||||
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(exception), exception);
|
||||
}
|
||||
|
||||
return exception;
|
||||
|
@@ -3,6 +3,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using Android.Content;
|
||||
using Android.Util;
|
||||
using KeePass.Util;
|
||||
using keepass2android.Io.ItemLocation;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
@@ -522,10 +523,10 @@ namespace keepass2android.Io
|
||||
{
|
||||
|
||||
if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString()))
|
||||
return new FileNotFoundException(e.Message);
|
||||
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
|
||||
if (e.Message.Contains("\n\n404 : ")
|
||||
) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround.
|
||||
return new FileNotFoundException(e.Message);
|
||||
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -1124,9 +1125,57 @@ namespace keepass2android.Io
|
||||
}
|
||||
|
||||
}
|
||||
public static async Task<DriveItem> GetOrCreateAppRootAsync(GraphServiceClient client, string dummyFileName = "welcome_at_kp2a.txt")
|
||||
{
|
||||
|
||||
|
||||
private async Task<List<FileDescription>> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, GraphServiceClient client)
|
||||
try
|
||||
{
|
||||
return await client.RequestAdapter.SendAsync(
|
||||
new Microsoft.Graph.Drives.Item.Items.Item.DriveItemItemRequestBuilder(
|
||||
new Dictionary<string, object> {
|
||||
{ "drive%2Did", "me" },
|
||||
{ "driveItem%2Did", "special/approot" }
|
||||
},
|
||||
client.RequestAdapter
|
||||
).ToGetRequestInformation(),
|
||||
static (p) => DriveItem.CreateFromDiscriminatorValue(p)
|
||||
);
|
||||
}
|
||||
catch (Microsoft.Kiota.Abstractions.ApiException ex) when (ex.ResponseStatusCode == 404)
|
||||
{
|
||||
// App folder doesn’t exist yet → create it by uploading a dummy file
|
||||
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("init"));
|
||||
|
||||
var uploadRequest = new RequestInformation
|
||||
{
|
||||
HttpMethod = Method.PUT,
|
||||
UrlTemplate = "{+baseurl}/me/drive/special/approot:/{filename}:/content",
|
||||
PathParameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "baseurl", client.RequestAdapter.BaseUrl },
|
||||
{ "filename", dummyFileName }
|
||||
},
|
||||
Content = stream
|
||||
};
|
||||
|
||||
var uploadedItem = await client.RequestAdapter.SendAsync<DriveItem>(
|
||||
uploadRequest,
|
||||
DriveItem.CreateFromDiscriminatorValue
|
||||
);
|
||||
|
||||
var parentId = uploadedItem.ParentReference.Id;
|
||||
|
||||
var parentItemRequest = new DriveItemRequestBuilder(
|
||||
$"{client.RequestAdapter.BaseUrl}/me/drive/items/{parentId}",
|
||||
client.RequestAdapter
|
||||
);
|
||||
|
||||
return await parentItemRequest.GetAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task<List<FileDescription>> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, GraphServiceClient client)
|
||||
{
|
||||
|
||||
List<FileDescription> result = [];
|
||||
@@ -1148,16 +1197,23 @@ namespace keepass2android.Io
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!CanListShares)
|
||||
return result;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
string? driveId = parentPath.DriveId;
|
||||
if (string.IsNullOrEmpty(driveId))
|
||||
{
|
||||
driveId = (await client.Me.Drive.GetAsync()).Id;
|
||||
}
|
||||
if ((string.IsNullOrEmpty(driveId)) && (drives?.Any() == true))
|
||||
{
|
||||
driveId = drives.First().Id;
|
||||
}
|
||||
|
||||
|
||||
if (!CanListShares)
|
||||
return result;
|
||||
|
||||
var sharedWithMeResponse = await client.Drives[driveId].SharedWithMe.GetAsSharedWithMeGetResponseAsync();
|
||||
|
||||
foreach (DriveItem i in sharedWithMeResponse?.Value ?? [])
|
||||
@@ -1165,13 +1221,22 @@ namespace keepass2android.Io
|
||||
var oneDrive2ItemLocation = parentPath.BuildShare(i.RemoteItem.Id, i.RemoteItem.Name, i.RemoteItem.WebUrl, i.RemoteItem.ParentReference.DriveId);
|
||||
FileDescription sharedFileEntry = new FileDescription()
|
||||
{
|
||||
CanWrite = true, CanRead = true, DisplayName = i.Name,
|
||||
IsDirectory = true,
|
||||
CanWrite = true,
|
||||
CanRead = true,
|
||||
DisplayName = i.Name,
|
||||
IsDirectory = (i.Folder != null) || ((i.RemoteItem != null) && (i.RemoteItem.Folder != null)),
|
||||
Path = oneDrive2ItemLocation.ToString()
|
||||
};
|
||||
result.Add(sharedFileEntry);
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logDebug("Failed to list shares: " + e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1329,6 +1394,8 @@ namespace keepass2android.Io
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override async Task<string?> GetSpecialFolder(
|
||||
OneDrive2ItemLocation<OneDrive2AppFolderPrefixContainer> itemLocation, GraphServiceClient client)
|
||||
{
|
||||
@@ -1361,8 +1428,55 @@ namespace keepass2android.Io
|
||||
{
|
||||
return drive.Name ?? MyOneDriveDisplayName;
|
||||
}
|
||||
public static async Task GetOrCreateAppRootAsync(GraphServiceClient client, string dummyFileName = "welcome_at_kp2a_app_folder.txt")
|
||||
{
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await client.RequestAdapter.SendAsync(
|
||||
new Microsoft.Graph.Drives.Item.Items.Item.DriveItemItemRequestBuilder(
|
||||
new Dictionary<string, object> {
|
||||
{ "drive%2Did", "me" },
|
||||
{ "driveItem%2Did", "special/approot" }
|
||||
},
|
||||
client.RequestAdapter
|
||||
).ToGetRequestInformation(),
|
||||
static (p) => DriveItem.CreateFromDiscriminatorValue(p)
|
||||
);
|
||||
//if this is successful, approot seems to exist
|
||||
}
|
||||
catch (Microsoft.Kiota.Abstractions.ApiException ex) when (ex.ResponseStatusCode == 404)
|
||||
{
|
||||
// App folder doesn’t exist yet → create it by uploading a dummy file
|
||||
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("init"));
|
||||
|
||||
var uploadRequest = new RequestInformation
|
||||
{
|
||||
HttpMethod = Method.PUT,
|
||||
UrlTemplate = "{+baseurl}/me/drive/special/approot:/{filename}:/content",
|
||||
PathParameters = new Dictionary<string, object>
|
||||
{
|
||||
{ "baseurl", client.RequestAdapter.BaseUrl },
|
||||
{ "filename", dummyFileName }
|
||||
},
|
||||
Content = stream
|
||||
};
|
||||
|
||||
await client.RequestAdapter.SendAsync<DriveItem>(
|
||||
uploadRequest,
|
||||
DriveItem.CreateFromDiscriminatorValue
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<List<FileDescription>> ListShares(OneDrive2ItemLocation<OneDrive2AppFolderPrefixContainer> parentPath, GraphServiceClient client)
|
||||
{
|
||||
await GetOrCreateAppRootAsync(client);
|
||||
return await base.ListShares(parentPath, client);
|
||||
}
|
||||
|
||||
public override bool CanListShares { get { return false; } }
|
||||
protected override string MyOneDriveDisplayName => "Keepass2Android App Folder";
|
||||
}
|
||||
|
@@ -16,6 +16,12 @@ namespace keepass2android.Io
|
||||
/// </summary>
|
||||
public class OneDriveFileStorage: IFileStorage
|
||||
{
|
||||
public OneDriveFileStorage(IKp2aApp app)
|
||||
{
|
||||
_app = app;
|
||||
}
|
||||
|
||||
private readonly IKp2aApp _app;
|
||||
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
@@ -26,10 +32,16 @@ namespace keepass2android.Io
|
||||
}
|
||||
}
|
||||
|
||||
private Exception GetDeprecatedMessage()
|
||||
string GetDeprecatedMessage()
|
||||
{
|
||||
return
|
||||
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select OneDrive again.";
|
||||
}
|
||||
|
||||
private Exception GetDeprecatedException()
|
||||
{
|
||||
return new Exception(
|
||||
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select One Drive again.");
|
||||
GetDeprecatedMessage());
|
||||
}
|
||||
|
||||
public bool UserShouldBackup
|
||||
@@ -39,133 +51,132 @@ namespace keepass2android.Io
|
||||
|
||||
public void Delete(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public string GetFileExtension(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool RequiresCredentials(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
public string IocToPath(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
|
||||
bool alwaysReturnSuccess)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
_app.ShowMessage(activity.Activity, GetDeprecatedMessage(), MessageSeverity.Error);
|
||||
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void OnResume(IFileStorageSetupActivity activity)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
|
||||
}
|
||||
|
||||
public void OnStart(IFileStorageSetupActivity activity)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
}
|
||||
|
||||
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
}
|
||||
|
||||
public string GetDisplayName(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
return "File using deprecated Microsoft API. Please update.";
|
||||
}
|
||||
|
||||
public string CreateFilePath(string parent, string newFilename)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
|
||||
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
||||
{
|
||||
throw GetDeprecatedMessage();
|
||||
throw GetDeprecatedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
617
src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
Normal file
@@ -0,0 +1,617 @@
|
||||
#if !NoNet
|
||||
using System.Net;
|
||||
using Android.Content;
|
||||
using keepass2android;
|
||||
using keepass2android.Io;
|
||||
using KeePassLib.Serialization;
|
||||
using SMBLibrary.Client;
|
||||
using SMBLibrary;
|
||||
using FileAttributes = SMBLibrary.FileAttributes;
|
||||
using KeePassLib.Utility;
|
||||
using Java.Nio.FileNio;
|
||||
|
||||
namespace Kp2aBusinessLogic.Io
|
||||
{
|
||||
public class SmbFileStorage : IFileStorage
|
||||
{
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get { yield return "smb"; }
|
||||
}
|
||||
|
||||
public bool UserShouldBackup
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public void Delete(IOConnectionInfo ioc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public struct SmbConnectionInfo
|
||||
{
|
||||
public string Host;
|
||||
public string Username;
|
||||
public string Password;
|
||||
public string? Domain;
|
||||
public string? Share;
|
||||
public string? LocalPath;
|
||||
|
||||
public static SmbConnectionInfo FromUrlAndCredentials(string url, string username, string password, string? domain)
|
||||
{
|
||||
string userDomain = username;
|
||||
if (domain != null)
|
||||
{
|
||||
userDomain = domain + "\\" + username;
|
||||
}
|
||||
if (url.StartsWith("smb://"))
|
||||
{
|
||||
url = url.Substring(6);
|
||||
}
|
||||
|
||||
if (url.StartsWith("\\\\"))
|
||||
{
|
||||
url = url.Substring(2);
|
||||
}
|
||||
|
||||
url = url.Replace("\\", "/");
|
||||
|
||||
string fullPath = "smb://" + WebUtility.UrlEncode(userDomain) + ":" + WebUtility.UrlEncode(password) + "@" + url;
|
||||
return new SmbConnectionInfo(new IOConnectionInfo() { Path = fullPath} );
|
||||
}
|
||||
|
||||
|
||||
public SmbConnectionInfo(IOConnectionInfo ioc)
|
||||
{
|
||||
string fullpath = ioc.Path;
|
||||
if (!fullpath.StartsWith("smb://"))
|
||||
{
|
||||
throw new Exception("Invalid smb path!");
|
||||
}
|
||||
|
||||
fullpath = fullpath.Substring(6);
|
||||
string[] authAndPath = fullpath.Split('@');
|
||||
if (authAndPath.Length != 2)
|
||||
{
|
||||
throw new Exception("Invalid smb path!");
|
||||
}
|
||||
|
||||
string[] userAndPwd = authAndPath[0].Split(':');
|
||||
if (userAndPwd.Length != 2)
|
||||
{
|
||||
throw new Exception("Invalid smb path!");
|
||||
}
|
||||
|
||||
string[] pathParts = authAndPath[1].Split('/');
|
||||
if (pathParts.Length < 1)
|
||||
{
|
||||
throw new Exception("Invalid smb path!");
|
||||
}
|
||||
|
||||
Host = pathParts[0];
|
||||
if (pathParts.Length > 1)
|
||||
{
|
||||
Share = pathParts[1];
|
||||
}
|
||||
LocalPath = String.Join("/", pathParts.Skip(2));
|
||||
if (LocalPath.EndsWith("/"))
|
||||
{
|
||||
LocalPath = LocalPath.Substring(0, LocalPath.Length - 1);
|
||||
}
|
||||
|
||||
Username = WebUtility.UrlDecode(userAndPwd[0]);
|
||||
if (Username.Contains("\\"))
|
||||
{
|
||||
string[] domainAndUser = Username.Split('\\');
|
||||
Domain = domainAndUser[0];
|
||||
Username = domainAndUser[1];
|
||||
}
|
||||
else Domain = null;
|
||||
|
||||
Password = WebUtility.UrlDecode(userAndPwd[1]);
|
||||
}
|
||||
|
||||
public string ToPath()
|
||||
{
|
||||
string domainUser = Username;
|
||||
if (Domain != null)
|
||||
{
|
||||
domainUser = Domain + "\\" + Username;
|
||||
}
|
||||
|
||||
return "smb://" + WebUtility.UrlEncode(domainUser) + ":" + WebUtility.UrlEncode(Password) + "@" + Host +
|
||||
"/" + Share + "/" + LocalPath;
|
||||
}
|
||||
|
||||
public string GetPathWithoutCredentials()
|
||||
{
|
||||
return "smb://" + Host + "/" + Share + "/" + LocalPath;
|
||||
}
|
||||
|
||||
public string GetLocalSmbPath()
|
||||
{
|
||||
return LocalPath?.Replace("/", "\\") ?? "";
|
||||
}
|
||||
|
||||
public SmbConnectionInfo GetParent()
|
||||
{
|
||||
SmbConnectionInfo parent = new SmbConnectionInfo
|
||||
{
|
||||
Host = Host,
|
||||
Username = Username,
|
||||
Password = Password,
|
||||
Domain = Domain,
|
||||
Share = Share
|
||||
};
|
||||
string[] pathParts = LocalPath?.Split('/') ?? [];
|
||||
if (pathParts.Length > 0)
|
||||
{
|
||||
parent.LocalPath = string.Join("/", pathParts.Take(pathParts.Length - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.LocalPath = "";
|
||||
parent.Share = "";
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
public string Stem()
|
||||
{
|
||||
return LocalPath?.Split('/').Last() ?? "";
|
||||
}
|
||||
|
||||
|
||||
public SmbConnectionInfo GetChild(string childName)
|
||||
{
|
||||
SmbConnectionInfo child = new SmbConnectionInfo();
|
||||
child.Host = Host;
|
||||
child.Username = Username;
|
||||
child.Password = Password;
|
||||
child.Domain = Domain;
|
||||
if (string.IsNullOrEmpty(Share))
|
||||
{
|
||||
child.Share = childName;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
child.Share = Share;
|
||||
var pathPartsList = LocalPath?.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToList() ?? [];
|
||||
pathPartsList.Add(childName);
|
||||
child.LocalPath = string.Join("/", pathPartsList);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return "smb://" + Host + "/" + Share + "/" + LocalPath;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SmbConnection: IDisposable
|
||||
{
|
||||
public SmbConnection(SmbConnectionInfo info)
|
||||
{
|
||||
_isLoggedIn = false;
|
||||
var isConnected = Client.Connect(info.Host, SMBTransportType.DirectTCPTransport);
|
||||
if (!isConnected)
|
||||
{
|
||||
throw new Exception($"Failed to connect to SMB server {info.Host}");
|
||||
}
|
||||
|
||||
var status = Client.Login(info.Domain ?? string.Empty, info.Username, info.Password);
|
||||
if (status != NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
throw new Exception($"Failed to login to SMB as {info.Username}");
|
||||
}
|
||||
|
||||
_isLoggedIn = true;
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Share))
|
||||
{
|
||||
FileStore = Client.TreeConnect(info.Share, out status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public readonly SMB2Client Client = new SMB2Client();
|
||||
|
||||
|
||||
public readonly ISMBFileStore? FileStore;
|
||||
private readonly bool _isLoggedIn;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FileStore?.Disconnect();
|
||||
|
||||
if (_isLoggedIn)
|
||||
Client.Logoff();
|
||||
|
||||
if (!Client.IsConnected) return;
|
||||
Client.Disconnect();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
|
||||
using SmbConnection conn = new SmbConnection(info);
|
||||
|
||||
if (conn.FileStore == null)
|
||||
{
|
||||
throw new Exception($"Failed to read to {info.GetPathWithoutCredentials()}");
|
||||
}
|
||||
|
||||
|
||||
NTStatus status = conn.FileStore.CreateFile(out var fileHandle, out _, info.GetLocalSmbPath(),
|
||||
AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read,
|
||||
CreateDisposition.FILE_OPEN,
|
||||
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
|
||||
|
||||
if (status != NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
throw new Exception($"Failed to open file {info.LocalPath}");
|
||||
}
|
||||
|
||||
var stream = new MemoryStream();
|
||||
long bytesRead = 0;
|
||||
while (true)
|
||||
{
|
||||
status = conn.FileStore.ReadFile(out var data, fileHandle, bytesRead, (int)conn.Client.MaxReadSize);
|
||||
if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE)
|
||||
{
|
||||
throw new Exception("Failed to read from file");
|
||||
}
|
||||
|
||||
if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bytesRead += data.Length;
|
||||
stream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
class SmbFileStorageWriteTransaction : IWriteTransaction
|
||||
{
|
||||
private bool UseFileTransaction { get; }
|
||||
private readonly string _path;
|
||||
private readonly string _uploadPath;
|
||||
private readonly SmbFileStorage _fileStorage;
|
||||
private MemoryStream? _memoryStream;
|
||||
|
||||
public SmbFileStorageWriteTransaction(string path, SmbFileStorage fileStorage, bool useFileTransaction)
|
||||
{
|
||||
UseFileTransaction = useFileTransaction;
|
||||
_path = path;
|
||||
if (useFileTransaction)
|
||||
{
|
||||
_uploadPath = _path + Guid.NewGuid().ToString().Substring(0, 8) + ".tmp";
|
||||
}
|
||||
else
|
||||
{
|
||||
_uploadPath = _path;
|
||||
}
|
||||
|
||||
|
||||
_fileStorage = fileStorage;
|
||||
_memoryStream = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryStream?.Dispose();
|
||||
}
|
||||
|
||||
public Stream OpenFile()
|
||||
{
|
||||
_memoryStream = new MemoryStream();
|
||||
return _memoryStream;
|
||||
}
|
||||
|
||||
public void CommitWrite()
|
||||
{
|
||||
_fileStorage.UploadData(new MemoryStream(_memoryStream!.ToArray()), new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath}));
|
||||
if (UseFileTransaction)
|
||||
{
|
||||
SmbConnectionInfo uploadPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath });
|
||||
SmbConnectionInfo finalPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _path });
|
||||
_fileStorage.RenameFile(uploadPath, finalPath);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void RenameFile(SmbConnectionInfo fromPath, SmbConnectionInfo toPath)
|
||||
{
|
||||
using var connection = new SmbConnection(fromPath);
|
||||
|
||||
// Open existing file
|
||||
var status = connection.FileStore!.CreateFile(out var handle, out _, fromPath.GetLocalSmbPath(), AccessMask.MAXIMUM_ALLOWED, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null);
|
||||
if (status != NTStatus.STATUS_SUCCESS)
|
||||
throw new Exception($"Failed to open {fromPath.LocalPath} for renaming!");
|
||||
|
||||
FileRenameInformationType2 renameInfo = new FileRenameInformationType2
|
||||
{
|
||||
FileName = toPath.GetLocalSmbPath(),
|
||||
ReplaceIfExists = true
|
||||
};
|
||||
connection.FileStore.SetFileInformation(handle, renameInfo);
|
||||
connection.FileStore.CloseFile(handle);
|
||||
|
||||
}
|
||||
|
||||
private void UploadData(Stream data, SmbConnectionInfo uploadPath)
|
||||
{
|
||||
using var connection = new SmbConnection(uploadPath);
|
||||
var status = connection.FileStore!.CreateFile(out var fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_CREATE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
|
||||
if (status == NTStatus.STATUS_OBJECT_NAME_COLLISION)
|
||||
status = connection.FileStore!.CreateFile(out fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OVERWRITE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
|
||||
if (status != NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
throw new Exception($"Failed to open {uploadPath.LocalPath} for writing!");
|
||||
}
|
||||
|
||||
long writeOffset = 0;
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
byte[] buffer = new byte[(int)connection.Client.MaxWriteSize];
|
||||
int bytesRead = data.Read(buffer, 0, buffer.Length);
|
||||
if (bytesRead < (int)connection.Client.MaxWriteSize)
|
||||
{
|
||||
Array.Resize(ref buffer, bytesRead);
|
||||
}
|
||||
|
||||
status = connection.FileStore.WriteFile(out _, fileHandle, writeOffset, buffer);
|
||||
if (status != NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
throw new Exception("Failed to write to file");
|
||||
}
|
||||
writeOffset += bytesRead;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
return new SmbFileStorageWriteTransaction(ioc.Path, this, useFileTransaction);
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
{
|
||||
return UrlUtil.StripExtension(
|
||||
UrlUtil.GetFileName(ioc.Path));
|
||||
|
||||
}
|
||||
|
||||
public string GetFileExtension(IOConnectionInfo ioc)
|
||||
{
|
||||
return UrlUtil.GetExtension(ioc.Path);
|
||||
}
|
||||
|
||||
public bool RequiresCredentials(IOConnectionInfo ioc)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static IEnumerable<FileDescription> ListShares(SmbConnection conn, SmbConnectionInfo parent)
|
||||
{
|
||||
foreach (string share in conn.Client.ListShares(out _))
|
||||
{
|
||||
yield return new FileDescription()
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
DisplayName = share,
|
||||
IsDirectory = true,
|
||||
Path = parent.GetChild(share).ToPath()
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||
{
|
||||
List<FileDescription> result = [];
|
||||
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
|
||||
using SmbConnection conn = new SmbConnection(info);
|
||||
if (string.IsNullOrEmpty(info.Share))
|
||||
{
|
||||
var shares = ListShares(conn, info).ToList();
|
||||
return shares;
|
||||
}
|
||||
|
||||
NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
|
||||
if (status == NTStatus.STATUS_SUCCESS)
|
||||
{
|
||||
conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation);
|
||||
foreach (var fi in fileList)
|
||||
{
|
||||
var fileDirectoryInformation = fi as FileDirectoryInformation;
|
||||
if (fileDirectoryInformation == null)
|
||||
continue;
|
||||
|
||||
if (fileDirectoryInformation.FileName is "." or "..")
|
||||
continue;
|
||||
|
||||
var fileDescription = FileDescriptionConvert(ioc, fileDirectoryInformation);
|
||||
|
||||
result.Add(fileDescription);
|
||||
}
|
||||
conn.FileStore.CloseFile(directoryHandle);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private FileDescription FileDescriptionConvert(IOConnectionInfo parentIoc,
|
||||
FileDirectoryInformation fileDirectoryInformation)
|
||||
{
|
||||
FileDescription fileDescription = new FileDescription
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
IsDirectory = (fileDirectoryInformation.FileAttributes & FileAttributes.Directory) != 0,
|
||||
DisplayName = fileDirectoryInformation.FileName
|
||||
};
|
||||
fileDescription.Path = CreateFilePath(parentIoc.Path, fileDescription.DisplayName);
|
||||
fileDescription.LastModified = fileDirectoryInformation.LastWriteTime;
|
||||
|
||||
fileDescription.SizeInBytes = fileDirectoryInformation.EndOfFile;
|
||||
return fileDescription;
|
||||
}
|
||||
|
||||
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||
{
|
||||
SmbConnectionInfo info = new SmbConnectionInfo(ioc);
|
||||
|
||||
if (string.IsNullOrEmpty(info.Share))
|
||||
{
|
||||
return new FileDescription
|
||||
{
|
||||
CanRead = true, CanWrite = true,
|
||||
DisplayName = info.Host,
|
||||
IsDirectory = true,
|
||||
Path = info.ToPath()
|
||||
};
|
||||
}
|
||||
|
||||
using SmbConnection conn = new SmbConnection(info);
|
||||
NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetParent().GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null);
|
||||
if (status != NTStatus.STATUS_SUCCESS) throw new Exception($"Failed to query details for {info.LocalPath}");
|
||||
conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, info.Stem(), FileInformationClass.FileDirectoryInformation);
|
||||
foreach (var fi in fileList)
|
||||
{
|
||||
var fileDirectoryInformation = fi as FileDirectoryInformation;
|
||||
if (fileDirectoryInformation == null)
|
||||
continue;
|
||||
|
||||
if (fileDirectoryInformation.FileName is "." or "..")
|
||||
continue;
|
||||
|
||||
return FileDescriptionConvert(ioc, fileDirectoryInformation);
|
||||
|
||||
|
||||
}
|
||||
conn.FileStore.CloseFile(directoryHandle);
|
||||
|
||||
throw new Exception($"Failed to query details for {info.LocalPath}");
|
||||
}
|
||||
|
||||
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public string IocToPath(IOConnectionInfo ioc)
|
||||
{
|
||||
return ioc.Path;
|
||||
}
|
||||
|
||||
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
||||
{
|
||||
activity.PerformManualFileSelect(isForSave, requestCode, protocolId);
|
||||
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
|
||||
bool alwaysReturnSuccess)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
activity.IocToIntent(intent, ioc);
|
||||
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnResume(IFileStorageSetupActivity activity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnStart(IFileStorageSetupActivity activity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string GetDisplayName(IOConnectionInfo ioc)
|
||||
{
|
||||
return new SmbConnectionInfo(ioc).ToDisplayString();
|
||||
}
|
||||
|
||||
public string CreateFilePath(string parent, string newFilename)
|
||||
{
|
||||
return new SmbConnectionInfo(new IOConnectionInfo() { Path = parent}).GetChild(newFilename).ToPath();
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||
{
|
||||
SmbConnectionInfo connectionInfo = new SmbConnectionInfo(ioc);
|
||||
return new IOConnectionInfo() { Path = connectionInfo.GetParent().ToPath() };
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
||||
{
|
||||
return new IOConnectionInfo() { Path = CreateFilePath(folderPath.Path, filename)};
|
||||
}
|
||||
|
||||
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -6,10 +6,12 @@ using System.Text;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
|
||||
using Keepass2android.Javafilestorage;
|
||||
#endif
|
||||
using KeePassLib.Serialization;
|
||||
@@ -19,8 +21,14 @@ namespace keepass2android.Io
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
public class WebDavFileStorage: JavaFileStorage
|
||||
{
|
||||
public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app)
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly WebDavStorage baseWebdavStorage;
|
||||
|
||||
public WebDavFileStorage(IKp2aApp app, int chunkSize) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler, chunkSize), app)
|
||||
{
|
||||
_app = app;
|
||||
baseWebdavStorage = (WebDavStorage)Jfs;
|
||||
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedProtocols
|
||||
@@ -75,6 +83,15 @@ namespace keepass2android.Io
|
||||
}
|
||||
return base.IocToPath(ioc);
|
||||
}
|
||||
|
||||
|
||||
public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize);
|
||||
return base.OpenWriteTransaction(ioc, useFileTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
}
|
@@ -1,33 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DefineConstants Condition="'$(Flavor)'=='NoNet'">NO_QR_SCANNER;EXCLUDE_JAVAFILESTORAGE;NoNet</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentFTP" Version="51.1.0" />
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.4" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.68.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" />
|
||||
<PackageReference Include="FluentFTP" Version="52.1.0" Condition="'$(Flavor)'!='NoNet'" />
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.4" Condition="'$(Flavor)'!='NoNet'" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'" />
|
||||
<PackageReference Include="SMBLibrary" Version="1.5.4" Condition="'$(Flavor)'!='NoNet'" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" />
|
||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj" />
|
||||
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" />
|
||||
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj" Condition="'$(Flavor)'!='NoNet'" />
|
||||
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj" />
|
||||
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj" />
|
||||
<ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Io/DropboxFileStorageKeysDummy.cs" />
|
||||
<Compile Remove="Io/DropboxFileStorageKeysDummy.cs" />
|
||||
<Content Remove="Io/DropboxFileStorageKeysDummy.cs" />
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Flavor)'=='NoNet'">
|
||||
<None Remove="Io/OneDrive2FileStorage.cs" />
|
||||
<Compile Remove="Io/OneDrive2FileStorage.cs" />
|
||||
<Content Remove="Io/OneDrive2FileStorage.cs" />
|
||||
<None Remove="Io/MegaFileStorage.cs" />
|
||||
<Compile Remove="Io/MegaFileStorage.cs" />
|
||||
<Content Remove="Io/MegaFileStorage.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="Io/GenerateSecrets.targets" />
|
||||
<ItemGroup>
|
||||
<Compile Include="Io/DropboxFileStorage.g.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
@@ -4,6 +4,7 @@ using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using Java.Net;
|
||||
using KeePass.Util;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
|
||||
@@ -94,15 +95,12 @@ namespace keepass2android
|
||||
}
|
||||
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
|
||||
{
|
||||
ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
|
||||
ShowErrorToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
|
||||
}
|
||||
|
||||
if (resultCode == Result.Ok)
|
||||
{
|
||||
Kp2aLog.Log("FileSelection returned "+data.DataString);
|
||||
//TODO: don't try to extract filename if content URI
|
||||
string filename = IntentToFilename(data);
|
||||
Kp2aLog.Log("FileSelection returned filename " + filename);
|
||||
if (filename != null)
|
||||
{
|
||||
if (filename.StartsWith("file://"))
|
||||
@@ -150,7 +148,7 @@ namespace keepass2android
|
||||
|
||||
protected abstract void StartFileChooser(string path, int requestCode, bool isForSave);
|
||||
|
||||
protected abstract void ShowToast(string text);
|
||||
protected abstract void ShowErrorToast(string text);
|
||||
|
||||
protected abstract void ShowInvalidSchemeMessage(string dataString);
|
||||
|
||||
@@ -208,7 +206,7 @@ namespace keepass2android
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message);
|
||||
ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + ExceptionUtil.GetErrorMessage(e));
|
||||
ReturnCancel();
|
||||
};
|
||||
}
|
||||
|
33
src/Kp2aBusinessLogic/Utils/ExceptionUtil.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace KeePass.Util
|
||||
{
|
||||
public class ExceptionUtil
|
||||
{
|
||||
|
||||
public static string GetErrorMessage(Exception e)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
string errorMessage = e.Message;
|
||||
if (e is Java.Lang.Exception javaException)
|
||||
{
|
||||
errorMessage = javaException.LocalizedMessage ?? javaException.Message ?? errorMessage;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using KeePass.Util;
|
||||
using KeePassLib.Cryptography;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
@@ -65,7 +66,7 @@ namespace keepass2android
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -90,8 +90,11 @@ namespace keepass2android
|
||||
PwDatabase pwDatabase = new PwDatabase();
|
||||
|
||||
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
||||
Kp2aLog.Log("LoadData: Retrieving stream");
|
||||
Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo);
|
||||
Kp2aLog.Log("LoadData: GetCurrentFileVersion");
|
||||
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
|
||||
Kp2aLog.Log("LoadData: PopulateDatabaseFromStream");
|
||||
PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat);
|
||||
LastFileVersion = fileVersion;
|
||||
|
||||
|
@@ -10,6 +10,7 @@ using Com.Keepassdroid.Database.Exception;
|
||||
#endif
|
||||
using Com.Keepassdroid.Database.Save;
|
||||
using Java.Util;
|
||||
using KeePass.Util;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Cryptography;
|
||||
using KeePassLib.Cryptography.Cipher;
|
||||
@@ -82,14 +83,13 @@ namespace keepass2android
|
||||
catch (Java.IO.FileNotFoundException e)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
e.Message, e);
|
||||
ExceptionUtil.GetErrorMessage(e), e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
if (e.Message == "Invalid key!")
|
||||
throw new InvalidCompositeKeyException();
|
||||
throw new Exception(e.LocalizedMessage ??
|
||||
e.Message ??
|
||||
throw new Exception(ExceptionUtil.GetErrorMessage(e) ??
|
||||
e.GetType().Name, e);
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ using Android.App;
|
||||
using Android.Content;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
using KeePass.Util;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
@@ -109,7 +110,7 @@ namespace keepass2android
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using KeePass.Util;
|
||||
using keepass2android.database.edit;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Keys;
|
||||
@@ -103,10 +104,10 @@ namespace keepass2android
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
string message = e.Message;
|
||||
string message = ExceptionUtil.GetErrorMessage(e);
|
||||
foreach (var innerException in e.InnerExceptions)
|
||||
{
|
||||
message = innerException.Message;
|
||||
message = ExceptionUtil.GetErrorMessage(innerException);
|
||||
// Override the message shown with the last (hopefully most recent) inner exception
|
||||
Kp2aLog.LogUnexpectedError(innerException);
|
||||
}
|
||||
@@ -116,14 +117,14 @@ namespace keepass2android
|
||||
catch (DuplicateUuidsException e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
||||
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!(e is InvalidCompositeKeyException))
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (e.Message ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
|
||||
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (ExceptionUtil.GetErrorMessage(e) ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,6 +138,7 @@ namespace keepass2android
|
||||
|
||||
Database TryLoad(MemoryStream databaseStream)
|
||||
{
|
||||
Kp2aLog.Log("LoadDb: Copying database in memory");
|
||||
//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters
|
||||
//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors.
|
||||
//Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice.
|
||||
@@ -145,6 +147,7 @@ namespace keepass2android
|
||||
workingCopy.Seek(0, SeekOrigin.Begin);
|
||||
//reset stream if we need to reuse it later:
|
||||
databaseStream.Seek(0, SeekOrigin.Begin);
|
||||
Kp2aLog.Log("LoadDb: Ready to start loading");
|
||||
//now let's go:
|
||||
try
|
||||
{
|
||||
|
@@ -29,6 +29,7 @@ using KeePassLib.Utility;
|
||||
using keepass2android.Io;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
using Exception = System.Exception;
|
||||
using KeePass.Util;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
@@ -187,7 +188,7 @@ namespace keepass2android
|
||||
}
|
||||
*/
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -222,8 +223,8 @@ namespace keepass2android
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Kp2aLog.Log("Error in worker thread of SaveDb: " + e);
|
||||
Finish(false, e.Message);
|
||||
Kp2aLog.Log("Error in worker thread of SaveDb: " + ExceptionUtil.GetErrorMessage(e));
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -233,7 +234,7 @@ namespace keepass2android
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e);
|
||||
Kp2aLog.Log("Error starting worker thread of SaveDb: "+e);
|
||||
Finish(false, e.Message);
|
||||
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-android</TargetFramework>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
25
src/build-scripts/build-java.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
#unset ANDROID_NDK_HOME ANDROID_NDK
|
||||
|
||||
pushd ../java/
|
||||
|
||||
pushd JavaFileStorageTest-AS
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
pushd KP2ASoftkeyboard_AS
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
pushd Keepass2AndroidPluginSDK2
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
pushd KP2AKdbLibrary
|
||||
./gradlew assemble
|
||||
popd
|
||||
|
||||
popd
|
6
src/build-scripts/build-native.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
pushd ../java/argon2
|
||||
ndk-build
|
||||
popd
|
28
src/build-scripts/linux-build.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Setup build environment
|
||||
* install Android SDK
|
||||
* install Android NDK
|
||||
* install dotnet8
|
||||
|
||||
```
|
||||
|
||||
#from https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-8.0.407-linux-x64-binaries
|
||||
wget https://download.visualstudio.microsoft.com/download/pr/9d07577e-f7bc-4d60-838d-f79c50b5c11a/459ef339396783db369e0432d6dc3d7e/dotnet-sdk-8.0.407-linux-x64.tar.gz
|
||||
mkdir -p $HOME/dotnet && tar zxf dotnet-sdk-8.0.407-linux-x64.tar.gz -C $HOME/dotnet
|
||||
export DOTNET_ROOT=$HOME/dotnet
|
||||
export PATH=$PATH:$HOME/dotnet
|
||||
|
||||
```
|
||||
|
||||
## Build Keepass2Android
|
||||
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/PhilippC/keepass2android.git
|
||||
cd keepass2android/src/build-scripts
|
||||
./build-java.sh && ./build-native.sh
|
||||
cd ..
|
||||
cd keepass2android-app
|
||||
ln -s Manifests/AndroidManifest_debug.xml AndroidManifest.xml
|
||||
dotnet workload restore
|
||||
dotnet restore
|
||||
dotnet build
|
||||
```
|
22
src/build-scripts/rename-output-apks.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BASE_DIR="${1}"
|
||||
|
||||
for arch_dir in "$BASE_DIR"/android-*/; do
|
||||
arch=$(basename "$arch_dir")
|
||||
arch=${arch#android-}
|
||||
APK_DIR="${arch_dir}publish"
|
||||
if [[ -d "$APK_DIR" ]]; then
|
||||
apk_path=$(find "$APK_DIR" -maxdepth 1 -type f -name "*.apk" | head -n1)
|
||||
if [[ -n "$apk_path" ]]; then
|
||||
base=$(basename "$apk_path" .apk)
|
||||
new_path="$APK_DIR/${base}-${arch}.apk"
|
||||
mv "$apk_path" "$new_path"
|
||||
echo "Renamed $apk_path to $new_path"
|
||||
else
|
||||
echo "No APK found in $APK_DIR"
|
||||
fi
|
||||
else
|
||||
echo "Directory $APK_DIR does not exist"
|
||||
fi
|
||||
done
|
@@ -6,8 +6,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
compileSdk 34
|
||||
targetSdkVersion 35
|
||||
compileSdk 35
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -51,4 +51,6 @@ dependencies {
|
||||
implementation 'com.pcloud.sdk:android:1.9.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
|
||||
implementation 'com.github.mwiede:jsch:2.27.2'
|
||||
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
public interface AgentConnector {
|
||||
String getName();
|
||||
boolean isAvailable();
|
||||
void query(Buffer buffer) throws AgentProxyException;
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
class AgentIdentity implements Identity {
|
||||
|
||||
private AgentProxy agent;
|
||||
private byte[] blob;
|
||||
private String comment;
|
||||
private String algname;
|
||||
AgentIdentity(AgentProxy agent, byte[] blob, String comment) {
|
||||
this.agent = agent;
|
||||
this.blob = blob;
|
||||
this.comment = comment;
|
||||
algname = Util.byte2str((new Buffer(blob)).getString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setPassphrase(byte[] passphrase) throws JSchException{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPublicKeyBlob() { return blob; }
|
||||
|
||||
@Override
|
||||
public byte[] getSignature(byte[] data){
|
||||
return agent.sign(blob, data, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature(byte[] data, String alg){
|
||||
return agent.sign(blob, data, alg);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean decrypt() {
|
||||
throw new RuntimeException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgName() { return algname; }
|
||||
|
||||
@Override
|
||||
public String getName() { return comment; }
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted() { return false; }
|
||||
|
||||
@Override
|
||||
public void clear() { }
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
public class AgentIdentityRepository implements IdentityRepository {
|
||||
|
||||
private AgentProxy agent;
|
||||
public AgentIdentityRepository(AgentConnector connector) {
|
||||
this.agent = new AgentProxy(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector<Identity> getIdentities() {
|
||||
return agent.getIdentities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(byte[] identity) {
|
||||
return agent.addIdentity(identity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(byte[] blob) {
|
||||
return agent.removeIdentity(blob);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll() {
|
||||
agent.removeAllIdentities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return agent.getConnector().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
if(agent.getConnector().isAvailable()){
|
||||
return RUNNING;
|
||||
}
|
||||
else {
|
||||
return NOTRUNNING;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,256 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2012 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
class AgentProxy {
|
||||
|
||||
private static final byte SSH_AGENTC_REQUEST_RSA_IDENTITIES = 1;
|
||||
private static final byte SSH_AGENT_RSA_IDENTITIES_ANSWER = 2;
|
||||
private static final byte SSH_AGENTC_RSA_CHALLENGE = 3;
|
||||
private static final byte SSH_AGENT_RSA_RESPONSE = 4;
|
||||
private static final byte SSH_AGENT_FAILURE = 5;
|
||||
private static final byte SSH_AGENT_SUCCESS = 6;
|
||||
private static final byte SSH_AGENTC_ADD_RSA_IDENTITY = 7;
|
||||
private static final byte SSH_AGENTC_REMOVE_RSA_IDENTITY = 8;
|
||||
private static final byte SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9;
|
||||
|
||||
private static final byte SSH2_AGENTC_REQUEST_IDENTITIES = 11;
|
||||
private static final byte SSH2_AGENT_IDENTITIES_ANSWER = 12;
|
||||
private static final byte SSH2_AGENTC_SIGN_REQUEST = 13;
|
||||
private static final byte SSH2_AGENT_SIGN_RESPONSE = 14;
|
||||
private static final byte SSH2_AGENTC_ADD_IDENTITY = 17;
|
||||
private static final byte SSH2_AGENTC_REMOVE_IDENTITY = 18;
|
||||
private static final byte SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19;
|
||||
|
||||
private static final byte SSH_AGENTC_ADD_SMARTCARD_KEY = 20;
|
||||
private static final byte SSH_AGENTC_REMOVE_SMARTCARD_KEY = 21;
|
||||
|
||||
private static final byte SSH_AGENTC_LOCK = 22;
|
||||
private static final byte SSH_AGENTC_UNLOCK = 23;
|
||||
|
||||
private static final byte SSH_AGENTC_ADD_RSA_ID_CONSTRAINED = 24;
|
||||
private static final byte SSH2_AGENTC_ADD_ID_CONSTRAINED = 25;
|
||||
private static final byte SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26;
|
||||
|
||||
private static final byte SSH_AGENT_CONSTRAIN_LIFETIME = 1;
|
||||
private static final byte SSH_AGENT_CONSTRAIN_CONFIRM = 2;
|
||||
|
||||
private static final byte SSH2_AGENT_FAILURE = 30;
|
||||
|
||||
private static final byte SSH_COM_AGENT2_FAILURE = 102;
|
||||
|
||||
//private static final byte SSH_AGENT_OLD_SIGNATURE = 0x1;
|
||||
private static final int SSH_AGENT_RSA_SHA2_256 = 0x2;
|
||||
private static final int SSH_AGENT_RSA_SHA2_512 = 0x4;
|
||||
|
||||
private static final int MAX_AGENT_IDENTITIES = 2048;
|
||||
|
||||
private final byte[] buf = new byte[1024];
|
||||
private final Buffer buffer = new Buffer(buf);
|
||||
|
||||
private AgentConnector connector;
|
||||
|
||||
AgentProxy(AgentConnector connector){
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
synchronized Vector<Identity> getIdentities() {
|
||||
Vector<Identity> identities = new Vector<>();
|
||||
|
||||
int required_size = 1 + 4;
|
||||
buffer.reset();
|
||||
buffer.checkFreeSize(required_size);
|
||||
buffer.putInt(required_size - 4);
|
||||
buffer.putByte(SSH2_AGENTC_REQUEST_IDENTITIES);
|
||||
|
||||
try {
|
||||
connector.query(buffer);
|
||||
}
|
||||
catch(AgentProxyException e){
|
||||
buffer.rewind();
|
||||
buffer.putByte(SSH_AGENT_FAILURE);
|
||||
return identities;
|
||||
}
|
||||
|
||||
int rcode = buffer.getByte();
|
||||
|
||||
//System.out.println(rcode == SSH2_AGENT_IDENTITIES_ANSWER);
|
||||
|
||||
if(rcode != SSH2_AGENT_IDENTITIES_ANSWER) {
|
||||
return identities;
|
||||
}
|
||||
|
||||
int count = buffer.getInt();
|
||||
//System.out.println(count);
|
||||
if(count <= 0 || count > MAX_AGENT_IDENTITIES) {
|
||||
return identities;
|
||||
}
|
||||
|
||||
for(int i=0; i<count; i++){
|
||||
byte[] blob = buffer.getString();
|
||||
String comment = Util.byte2str(buffer.getString());
|
||||
identities.add(new AgentIdentity(this, blob, comment));
|
||||
}
|
||||
|
||||
return identities;
|
||||
}
|
||||
|
||||
synchronized byte[] sign(byte[] blob, byte[] data, String alg) {
|
||||
int flags = 0x0;
|
||||
if(alg != null) {
|
||||
if(alg.equals("rsa-sha2-256")) {
|
||||
flags = SSH_AGENT_RSA_SHA2_256;
|
||||
}
|
||||
else if(alg.equals("rsa-sha2-512")) {
|
||||
flags = SSH_AGENT_RSA_SHA2_512;
|
||||
}
|
||||
}
|
||||
|
||||
int required_size = 1 + 4*4 + blob.length + data.length;
|
||||
buffer.reset();
|
||||
buffer.checkFreeSize(required_size);
|
||||
buffer.putInt(required_size - 4);
|
||||
buffer.putByte(SSH2_AGENTC_SIGN_REQUEST);
|
||||
buffer.putString(blob);
|
||||
buffer.putString(data);
|
||||
buffer.putInt(flags);
|
||||
|
||||
try {
|
||||
connector.query(buffer);
|
||||
}
|
||||
catch(AgentProxyException e){
|
||||
buffer.rewind();
|
||||
buffer.putByte(SSH_AGENT_FAILURE);
|
||||
}
|
||||
|
||||
int rcode = buffer.getByte();
|
||||
|
||||
//System.out.println(rcode == SSH2_AGENT_SIGN_RESPONSE);
|
||||
|
||||
if(rcode != SSH2_AGENT_SIGN_RESPONSE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return buffer.getString();
|
||||
}
|
||||
|
||||
synchronized boolean removeIdentity(byte[] blob) {
|
||||
int required_size = 1 + 4*2 + blob.length;
|
||||
buffer.reset();
|
||||
buffer.checkFreeSize(required_size);
|
||||
buffer.putInt(required_size - 4);
|
||||
buffer.putByte(SSH2_AGENTC_REMOVE_IDENTITY);
|
||||
buffer.putString(blob);
|
||||
|
||||
try {
|
||||
connector.query(buffer);
|
||||
}
|
||||
catch(AgentProxyException e){
|
||||
buffer.rewind();
|
||||
buffer.putByte(SSH_AGENT_FAILURE);
|
||||
}
|
||||
|
||||
int rcode = buffer.getByte();
|
||||
|
||||
//System.out.println(rcode == SSH_AGENT_SUCCESS);
|
||||
|
||||
return rcode == SSH_AGENT_SUCCESS;
|
||||
}
|
||||
|
||||
synchronized void removeAllIdentities() {
|
||||
int required_size = 1 + 4;
|
||||
buffer.reset();
|
||||
buffer.checkFreeSize(required_size);
|
||||
buffer.putInt(required_size - 4);
|
||||
buffer.putByte(SSH2_AGENTC_REMOVE_ALL_IDENTITIES);
|
||||
|
||||
try {
|
||||
connector.query(buffer);
|
||||
}
|
||||
catch(AgentProxyException e){
|
||||
buffer.rewind();
|
||||
buffer.putByte(SSH_AGENT_FAILURE);
|
||||
}
|
||||
|
||||
//int rcode = buffer.getByte();
|
||||
|
||||
//System.out.println(rcode == SSH_AGENT_SUCCESS);
|
||||
}
|
||||
|
||||
synchronized boolean addIdentity(byte[] identity) {
|
||||
int required_size = 1 + 4 + identity.length;
|
||||
buffer.reset();
|
||||
buffer.checkFreeSize(required_size);
|
||||
buffer.putInt(required_size - 4);
|
||||
buffer.putByte(SSH2_AGENTC_ADD_IDENTITY);
|
||||
buffer.putByte(identity);
|
||||
|
||||
try {
|
||||
connector.query(buffer);
|
||||
}
|
||||
catch(AgentProxyException e){
|
||||
buffer.rewind();
|
||||
buffer.putByte(SSH_AGENT_FAILURE);
|
||||
}
|
||||
|
||||
int rcode = buffer.getByte();
|
||||
|
||||
//System.out.println(rcode == SSH_AGENT_SUCCESS);
|
||||
|
||||
return rcode == SSH_AGENT_SUCCESS;
|
||||
}
|
||||
|
||||
synchronized boolean isRunning(){
|
||||
int required_size = 1 + 4;
|
||||
buffer.reset();
|
||||
buffer.checkFreeSize(required_size);
|
||||
buffer.putInt(required_size - 4);
|
||||
buffer.putByte(SSH2_AGENTC_REQUEST_IDENTITIES);
|
||||
|
||||
try {
|
||||
connector.query(buffer);
|
||||
}
|
||||
catch(AgentProxyException e){
|
||||
return false;
|
||||
}
|
||||
|
||||
int rcode = buffer.getByte();
|
||||
|
||||
//System.out.println(rcode == SSH2_AGENT_IDENTITIES_ANSWER);
|
||||
|
||||
return rcode == SSH2_AGENT_IDENTITIES_ANSWER;
|
||||
}
|
||||
|
||||
synchronized AgentConnector getConnector() {
|
||||
return connector;
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2011 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
public class AgentProxyException extends Exception {
|
||||
private static final long serialVersionUID=-1L;
|
||||
public AgentProxyException(String message){
|
||||
super(message);
|
||||
}
|
||||
public AgentProxyException(String message, Throwable e){
|
||||
super(message, e);
|
||||
}
|
||||
}
|
@@ -1,297 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
public class Buffer{
|
||||
final byte[] tmp=new byte[4];
|
||||
byte[] buffer;
|
||||
int index;
|
||||
int s;
|
||||
public Buffer(int size){
|
||||
buffer=new byte[size];
|
||||
index=0;
|
||||
s=0;
|
||||
}
|
||||
public Buffer(byte[] buffer){
|
||||
this.buffer=buffer;
|
||||
index=0;
|
||||
s=0;
|
||||
}
|
||||
public Buffer(){ this(1024*10*2); }
|
||||
public void putByte(byte foo){
|
||||
buffer[index++]=foo;
|
||||
}
|
||||
public void putByte(byte[] foo) {
|
||||
putByte(foo, 0, foo.length);
|
||||
}
|
||||
public void putByte(byte[] foo, int begin, int length) {
|
||||
System.arraycopy(foo, begin, buffer, index, length);
|
||||
index+=length;
|
||||
}
|
||||
public void putString(byte[] foo){
|
||||
putString(foo, 0, foo.length);
|
||||
}
|
||||
public void putString(byte[] foo, int begin, int length) {
|
||||
putInt(length);
|
||||
putByte(foo, begin, length);
|
||||
}
|
||||
public void putInt(int val) {
|
||||
tmp[0]=(byte)(val >>> 24);
|
||||
tmp[1]=(byte)(val >>> 16);
|
||||
tmp[2]=(byte)(val >>> 8);
|
||||
tmp[3]=(byte)(val);
|
||||
System.arraycopy(tmp, 0, buffer, index, 4);
|
||||
index+=4;
|
||||
}
|
||||
public void putLong(long val) {
|
||||
tmp[0]=(byte)(val >>> 56);
|
||||
tmp[1]=(byte)(val >>> 48);
|
||||
tmp[2]=(byte)(val >>> 40);
|
||||
tmp[3]=(byte)(val >>> 32);
|
||||
System.arraycopy(tmp, 0, buffer, index, 4);
|
||||
tmp[0]=(byte)(val >>> 24);
|
||||
tmp[1]=(byte)(val >>> 16);
|
||||
tmp[2]=(byte)(val >>> 8);
|
||||
tmp[3]=(byte)(val);
|
||||
System.arraycopy(tmp, 0, buffer, index+4, 4);
|
||||
index+=8;
|
||||
}
|
||||
void skip(int n) {
|
||||
index+=n;
|
||||
}
|
||||
void putPad(int n) {
|
||||
while(n>0){
|
||||
buffer[index++]=(byte)0;
|
||||
n--;
|
||||
}
|
||||
}
|
||||
public void putMPInt(byte[] foo){
|
||||
int i=foo.length;
|
||||
if((foo[0]&0x80)!=0){
|
||||
i++;
|
||||
putInt(i);
|
||||
putByte((byte)0);
|
||||
}
|
||||
else{
|
||||
putInt(i);
|
||||
}
|
||||
putByte(foo);
|
||||
}
|
||||
public int getLength(){
|
||||
return index-s;
|
||||
}
|
||||
public int getOffSet(){
|
||||
return s;
|
||||
}
|
||||
public void setOffSet(int s){
|
||||
this.s=s;
|
||||
}
|
||||
public long getLong(){
|
||||
long foo = getInt()&0xffffffffL;
|
||||
foo = ((foo<<32)) | (getInt()&0xffffffffL);
|
||||
return foo;
|
||||
}
|
||||
public int getInt(){
|
||||
int foo = getShort();
|
||||
foo = ((foo<<16)&0xffff0000) | (getShort()&0xffff);
|
||||
return foo;
|
||||
}
|
||||
public long getUInt(){
|
||||
long foo = 0L;
|
||||
long bar = 0L;
|
||||
foo = getByte();
|
||||
foo = ((foo<<8)&0xff00)|(getByte()&0xff);
|
||||
bar = getByte();
|
||||
bar = ((bar<<8)&0xff00)|(getByte()&0xff);
|
||||
foo = ((foo<<16)&0xffff0000) | (bar&0xffff);
|
||||
return foo;
|
||||
}
|
||||
int getShort() {
|
||||
int foo = getByte();
|
||||
foo = ((foo<<8)&0xff00)|(getByte()&0xff);
|
||||
return foo;
|
||||
}
|
||||
public int getByte() {
|
||||
return (buffer[s++]&0xff);
|
||||
}
|
||||
public void getByte(byte[] foo) {
|
||||
getByte(foo, 0, foo.length);
|
||||
}
|
||||
void getByte(byte[] foo, int start, int len) {
|
||||
System.arraycopy(buffer, s, foo, start, len);
|
||||
s+=len;
|
||||
}
|
||||
public int getByte(int len) {
|
||||
int foo=s;
|
||||
s+=len;
|
||||
return foo;
|
||||
}
|
||||
public byte[] getMPInt() {
|
||||
int i=getInt(); // uint32
|
||||
if(i<0 || // bigger than 0x7fffffff
|
||||
i>8*1024){
|
||||
// TODO: an exception should be thrown.
|
||||
i = 8*1024; // the session will be broken, but working around OOME.
|
||||
}
|
||||
byte[] foo=new byte[i];
|
||||
getByte(foo, 0, i);
|
||||
return foo;
|
||||
}
|
||||
public byte[] getMPIntBits() {
|
||||
int bits=getInt();
|
||||
int bytes=(bits+7)/8;
|
||||
byte[] foo=new byte[bytes];
|
||||
getByte(foo, 0, bytes);
|
||||
if((foo[0]&0x80)!=0){
|
||||
byte[] bar=new byte[foo.length+1];
|
||||
bar[0]=0; // ??
|
||||
System.arraycopy(foo, 0, bar, 1, foo.length);
|
||||
foo=bar;
|
||||
}
|
||||
return foo;
|
||||
}
|
||||
public byte[] getString() {
|
||||
int i = getInt(); // uint32
|
||||
if(i<0 || // bigger than 0x7fffffff
|
||||
i>256*1024){
|
||||
// TODO: an exception should be thrown.
|
||||
i = 256*1024; // the session will be broken, but working around OOME.
|
||||
}
|
||||
byte[] foo=new byte[i];
|
||||
getByte(foo, 0, i);
|
||||
return foo;
|
||||
}
|
||||
byte[] getString(int[]start, int[]len) {
|
||||
int i=getInt();
|
||||
start[0]=getByte(i);
|
||||
len[0]=i;
|
||||
return buffer;
|
||||
}
|
||||
public void reset(){
|
||||
index=0;
|
||||
s=0;
|
||||
}
|
||||
public void shift(){
|
||||
if(s==0)return;
|
||||
System.arraycopy(buffer, s, buffer, 0, index-s);
|
||||
index=index-s;
|
||||
s=0;
|
||||
}
|
||||
void rewind(){
|
||||
s=0;
|
||||
}
|
||||
|
||||
byte getCommand(){
|
||||
return buffer[5];
|
||||
}
|
||||
|
||||
void checkFreeSize(int n){
|
||||
int size = index+n+Session.buffer_margin;
|
||||
if(buffer.length<size){
|
||||
int i = buffer.length*2;
|
||||
if(i<size) i = size;
|
||||
byte[] tmp = new byte[i];
|
||||
System.arraycopy(buffer, 0, tmp, 0, index);
|
||||
buffer = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
byte[][] getBytes(int n, String msg) throws JSchException {
|
||||
byte[][] tmp = new byte[n][];
|
||||
for(int i = 0; i < n; i++){
|
||||
int j = getInt();
|
||||
if(getLength() < j){
|
||||
throw new JSchException(msg);
|
||||
}
|
||||
tmp[i] = new byte[j];
|
||||
getByte(tmp[i]);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
static Buffer fromBytes(byte[]... args){
|
||||
int length = args.length*4;
|
||||
for(int i = 0; i < args.length; i++){
|
||||
length += args[i].length;
|
||||
}
|
||||
Buffer buf = new Buffer(length);
|
||||
for(int i = 0; i < args.length; i++){
|
||||
buf.putString(args[i]);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
|
||||
static Buffer fromBytes(byte[][] args){
|
||||
int length = args.length*4;
|
||||
for(int i = 0; i < args.length; i++){
|
||||
length += args[i].length;
|
||||
}
|
||||
Buffer buf = new Buffer(length);
|
||||
for(int i = 0; i < args.length; i++){
|
||||
buf.putString(args[i]);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
static String[] chars={
|
||||
"0","1","2","3","4","5","6","7","8","9", "a","b","c","d","e","f"
|
||||
};
|
||||
static void dump_buffer(){
|
||||
int foo;
|
||||
for(int i=0; i<tmp_buffer_index; i++){
|
||||
foo=tmp_buffer[i]&0xff;
|
||||
System.err.print(chars[(foo>>>4)&0xf]);
|
||||
System.err.print(chars[foo&0xf]);
|
||||
if(i%16==15){
|
||||
System.err.println("");
|
||||
continue;
|
||||
}
|
||||
if(i>0 && i%2==1){
|
||||
System.err.print(" ");
|
||||
}
|
||||
}
|
||||
System.err.println("");
|
||||
}
|
||||
static void dump(byte[] b){
|
||||
dump(b, 0, b.length);
|
||||
}
|
||||
static void dump(byte[] b, int s, int l){
|
||||
for(int i=s; i<s+l; i++){
|
||||
System.err.print(Integer.toHexString(b[i]&0xff)+":");
|
||||
}
|
||||
System.err.println("");
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@@ -1,782 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Vector;
|
||||
|
||||
public abstract class Channel{
|
||||
|
||||
static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION= 91;
|
||||
static final int SSH_MSG_CHANNEL_OPEN_FAILURE= 92;
|
||||
static final int SSH_MSG_CHANNEL_WINDOW_ADJUST= 93;
|
||||
|
||||
static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED= 1;
|
||||
static final int SSH_OPEN_CONNECT_FAILED= 2;
|
||||
static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE= 3;
|
||||
static final int SSH_OPEN_RESOURCE_SHORTAGE= 4;
|
||||
|
||||
static int index=0;
|
||||
private static Vector<Channel> pool=new Vector<>();
|
||||
static Channel getChannel(String type, Session session){
|
||||
Channel ret = null;
|
||||
if(type.equals("session")){
|
||||
ret = new ChannelSession();
|
||||
}
|
||||
if(type.equals("shell")){
|
||||
ret = new ChannelShell();
|
||||
}
|
||||
if(type.equals("exec")){
|
||||
ret = new ChannelExec();
|
||||
}
|
||||
if(type.equals("x11")){
|
||||
ret = new ChannelX11();
|
||||
}
|
||||
if(type.equals("auth-agent@openssh.com")){
|
||||
ret = new ChannelAgentForwarding();
|
||||
}
|
||||
if(type.equals("direct-tcpip")){
|
||||
ret = new ChannelDirectTCPIP();
|
||||
}
|
||||
if(type.equals("forwarded-tcpip")){
|
||||
ret = new ChannelForwardedTCPIP();
|
||||
}
|
||||
if(type.equals("sftp")){
|
||||
ret = new ChannelSftp();
|
||||
}
|
||||
if(type.equals("subsystem")){
|
||||
ret = new ChannelSubsystem();
|
||||
}
|
||||
if(type.equals("direct-streamlocal@openssh.com")){
|
||||
ret = new ChannelDirectStreamLocal();
|
||||
}
|
||||
if (ret == null) {
|
||||
return null;
|
||||
}
|
||||
ret.setSession(session);
|
||||
return ret;
|
||||
}
|
||||
static Channel getChannel(int id, Session session){
|
||||
synchronized(pool){
|
||||
for(int i=0; i<pool.size(); i++){
|
||||
Channel c=pool.elementAt(i);
|
||||
if(c.id==id && c.session==session) return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static void del(Channel c){
|
||||
synchronized(pool){
|
||||
pool.removeElement(c);
|
||||
}
|
||||
}
|
||||
|
||||
int id;
|
||||
volatile int recipient=-1;
|
||||
protected byte[] type=Util.str2byte("foo");
|
||||
volatile int lwsize_max=0x100000;
|
||||
volatile int lwsize=lwsize_max; // local initial window size
|
||||
volatile int lmpsize=0x4000; // local maximum packet size
|
||||
|
||||
volatile long rwsize=0; // remote initial window size
|
||||
volatile int rmpsize=0; // remote maximum packet size
|
||||
|
||||
IO io=null;
|
||||
Thread thread=null;
|
||||
|
||||
volatile boolean eof_local=false;
|
||||
volatile boolean eof_remote=false;
|
||||
|
||||
volatile boolean close=false;
|
||||
volatile boolean connected=false;
|
||||
volatile boolean open_confirmation=false;
|
||||
|
||||
volatile int exitstatus=-1;
|
||||
|
||||
volatile int reply=0;
|
||||
volatile int connectTimeout=0;
|
||||
|
||||
protected Session session;
|
||||
|
||||
int notifyme=0;
|
||||
|
||||
Channel(){
|
||||
synchronized(pool){
|
||||
id=index++;
|
||||
pool.addElement(this);
|
||||
}
|
||||
}
|
||||
synchronized void setRecipient(int foo){
|
||||
this.recipient=foo;
|
||||
if(notifyme>0)
|
||||
notifyAll();
|
||||
}
|
||||
int getRecipient(){
|
||||
return recipient;
|
||||
}
|
||||
|
||||
void init() throws JSchException {
|
||||
}
|
||||
|
||||
public void connect() throws JSchException{
|
||||
connect(0);
|
||||
}
|
||||
|
||||
public void connect(int connectTimeout) throws JSchException{
|
||||
this.connectTimeout=connectTimeout;
|
||||
try{
|
||||
sendChannelOpen();
|
||||
start();
|
||||
}
|
||||
catch(Exception e){
|
||||
connected=false;
|
||||
disconnect();
|
||||
if(e instanceof JSchException)
|
||||
throw (JSchException)e;
|
||||
throw new JSchException(e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setXForwarding(boolean foo){
|
||||
}
|
||||
|
||||
public void start() throws JSchException{}
|
||||
|
||||
public boolean isEOF() {return eof_remote;}
|
||||
|
||||
void getData(Buffer buf){
|
||||
setRecipient(buf.getInt());
|
||||
setRemoteWindowSize(buf.getUInt());
|
||||
setRemotePacketSize(buf.getInt());
|
||||
}
|
||||
|
||||
public void setInputStream(InputStream in){
|
||||
io.setInputStream(in, false);
|
||||
}
|
||||
public void setInputStream(InputStream in, boolean dontclose){
|
||||
io.setInputStream(in, dontclose);
|
||||
}
|
||||
public void setOutputStream(OutputStream out){
|
||||
io.setOutputStream(out, false);
|
||||
}
|
||||
public void setOutputStream(OutputStream out, boolean dontclose){
|
||||
io.setOutputStream(out, dontclose);
|
||||
}
|
||||
public void setExtOutputStream(OutputStream out){
|
||||
io.setExtOutputStream(out, false);
|
||||
}
|
||||
public void setExtOutputStream(OutputStream out, boolean dontclose){
|
||||
io.setExtOutputStream(out, dontclose);
|
||||
}
|
||||
public InputStream getInputStream() throws IOException {
|
||||
int max_input_buffer_size = 32*1024;
|
||||
try {
|
||||
max_input_buffer_size =
|
||||
Integer.parseInt(getSession().getConfig("max_input_buffer_size"));
|
||||
}
|
||||
catch(Exception e){}
|
||||
PipedInputStream in =
|
||||
new MyPipedInputStream(
|
||||
32*1024, // this value should be customizable.
|
||||
max_input_buffer_size
|
||||
);
|
||||
boolean resizable = 32*1024<max_input_buffer_size;
|
||||
io.setOutputStream(new PassiveOutputStream(in, resizable), false);
|
||||
return in;
|
||||
}
|
||||
public InputStream getExtInputStream() throws IOException {
|
||||
int max_input_buffer_size = 32*1024;
|
||||
try {
|
||||
max_input_buffer_size =
|
||||
Integer.parseInt(getSession().getConfig("max_input_buffer_size"));
|
||||
}
|
||||
catch(Exception e){}
|
||||
PipedInputStream in =
|
||||
new MyPipedInputStream(
|
||||
32*1024, // this value should be customizable.
|
||||
max_input_buffer_size
|
||||
);
|
||||
boolean resizable = 32*1024<max_input_buffer_size;
|
||||
io.setExtOutputStream(new PassiveOutputStream(in, resizable), false);
|
||||
return in;
|
||||
}
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
|
||||
final Channel channel=this;
|
||||
OutputStream out=new OutputStream(){
|
||||
private int dataLen=0;
|
||||
private Buffer buffer=null;
|
||||
private Packet packet=null;
|
||||
private boolean closed=false;
|
||||
private synchronized void init() throws IOException{
|
||||
buffer=new Buffer(rmpsize);
|
||||
packet=new Packet(buffer);
|
||||
|
||||
byte[] _buf=buffer.buffer;
|
||||
if(_buf.length-(14+0)-Session.buffer_margin<=0){
|
||||
buffer=null;
|
||||
packet=null;
|
||||
throw new IOException("failed to initialize the channel.");
|
||||
}
|
||||
|
||||
}
|
||||
byte[] b=new byte[1];
|
||||
@Override
|
||||
public void write(int w) throws IOException{
|
||||
b[0]=(byte)w;
|
||||
write(b, 0, 1);
|
||||
}
|
||||
@Override
|
||||
public void write(byte[] buf, int s, int l) throws IOException{
|
||||
if(packet==null){
|
||||
init();
|
||||
}
|
||||
|
||||
if(closed){
|
||||
throw new IOException("Already closed");
|
||||
}
|
||||
|
||||
byte[] _buf=buffer.buffer;
|
||||
int _bufl=_buf.length;
|
||||
while(l>0){
|
||||
int _l=l;
|
||||
if(l>_bufl-(14+dataLen)-Session.buffer_margin){
|
||||
_l=_bufl-(14+dataLen)-Session.buffer_margin;
|
||||
}
|
||||
|
||||
if(_l<=0){
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
System.arraycopy(buf, s, _buf, 14+dataLen, _l);
|
||||
dataLen+=_l;
|
||||
s+=_l;
|
||||
l-=_l;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException{
|
||||
if(closed){
|
||||
throw new IOException("Already closed");
|
||||
}
|
||||
if(dataLen==0)
|
||||
return;
|
||||
packet.reset();
|
||||
buffer.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
|
||||
buffer.putInt(recipient);
|
||||
buffer.putInt(dataLen);
|
||||
buffer.skip(dataLen);
|
||||
try{
|
||||
int foo=dataLen;
|
||||
dataLen=0;
|
||||
synchronized(channel){
|
||||
if(!channel.close)
|
||||
getSession().write(packet, channel, foo);
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
close();
|
||||
throw new IOException(e.toString(), e);
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException{
|
||||
if(packet==null){
|
||||
try{
|
||||
init();
|
||||
}
|
||||
catch(IOException e){
|
||||
// close should be finished silently.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(closed){
|
||||
return;
|
||||
}
|
||||
if(dataLen>0){
|
||||
flush();
|
||||
}
|
||||
channel.eof();
|
||||
closed=true;
|
||||
}
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
||||
static class MyPipedInputStream extends PipedInputStream{
|
||||
private int BUFFER_SIZE = 1024;
|
||||
private int max_buffer_size = BUFFER_SIZE;
|
||||
MyPipedInputStream() throws IOException{ super(); }
|
||||
MyPipedInputStream(int size) throws IOException{
|
||||
super();
|
||||
buffer=new byte[size];
|
||||
BUFFER_SIZE = size;
|
||||
max_buffer_size = size;
|
||||
}
|
||||
MyPipedInputStream(int size, int max_buffer_size) throws IOException{
|
||||
this(size);
|
||||
this.max_buffer_size = max_buffer_size;
|
||||
}
|
||||
MyPipedInputStream(PipedOutputStream out) throws IOException{ super(out); }
|
||||
MyPipedInputStream(PipedOutputStream out, int size) throws IOException{
|
||||
super(out);
|
||||
buffer=new byte[size];
|
||||
BUFFER_SIZE=size;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: We should have our own Piped[I/O]Stream implementation.
|
||||
* Before accepting data, JDK's PipedInputStream will check the existence of
|
||||
* reader thread, and if it is not alive, the stream will be closed.
|
||||
* That behavior may cause the problem if multiple threads make access to it.
|
||||
*/
|
||||
public synchronized void updateReadSide() throws IOException {
|
||||
if(available() != 0){ // not empty
|
||||
return;
|
||||
}
|
||||
in = 0;
|
||||
out = 0;
|
||||
buffer[in++] = 0;
|
||||
read();
|
||||
}
|
||||
|
||||
private int freeSpace(){
|
||||
int size = 0;
|
||||
if(out < in) {
|
||||
size = buffer.length-in;
|
||||
}
|
||||
else if(in < out){
|
||||
if(in == -1) size = buffer.length;
|
||||
else size = out - in;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
synchronized void checkSpace(int len) throws IOException {
|
||||
int size = freeSpace();
|
||||
if(size<len){
|
||||
int datasize=buffer.length-size;
|
||||
int foo = buffer.length;
|
||||
while((foo - datasize) < len){
|
||||
foo*=2;
|
||||
}
|
||||
|
||||
if(foo > max_buffer_size){
|
||||
foo = max_buffer_size;
|
||||
}
|
||||
if((foo - datasize) < len) return;
|
||||
|
||||
byte[] tmp = new byte[foo];
|
||||
if(out < in) {
|
||||
System.arraycopy(buffer, 0, tmp, 0, buffer.length);
|
||||
}
|
||||
else if(in < out){
|
||||
if(in == -1) {
|
||||
}
|
||||
else {
|
||||
System.arraycopy(buffer, 0, tmp, 0, in);
|
||||
System.arraycopy(buffer, out,
|
||||
tmp, tmp.length-(buffer.length-out),
|
||||
(buffer.length-out));
|
||||
out = tmp.length-(buffer.length-out);
|
||||
}
|
||||
}
|
||||
else if(in == out){
|
||||
System.arraycopy(buffer, 0, tmp, 0, buffer.length);
|
||||
in=buffer.length;
|
||||
}
|
||||
buffer=tmp;
|
||||
}
|
||||
else if(buffer.length == size && size > BUFFER_SIZE) {
|
||||
int i = size/2;
|
||||
if(i<BUFFER_SIZE) i = BUFFER_SIZE;
|
||||
byte[] tmp = new byte[i];
|
||||
buffer=tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
void setLocalWindowSizeMax(int foo){ this.lwsize_max=foo; }
|
||||
void setLocalWindowSize(int foo){ this.lwsize=foo; }
|
||||
void setLocalPacketSize(int foo){ this.lmpsize=foo; }
|
||||
synchronized void setRemoteWindowSize(long foo){ this.rwsize=foo; }
|
||||
synchronized void addRemoteWindowSize(long foo){
|
||||
this.rwsize+=foo;
|
||||
if(notifyme>0)
|
||||
notifyAll();
|
||||
}
|
||||
void setRemotePacketSize(int foo){ this.rmpsize=foo; }
|
||||
|
||||
abstract void run();
|
||||
|
||||
void write(byte[] foo) throws IOException {
|
||||
write(foo, 0, foo.length);
|
||||
}
|
||||
void write(byte[] foo, int s, int l) throws IOException {
|
||||
try{
|
||||
io.put(foo, s, l);
|
||||
}catch(NullPointerException e){}
|
||||
}
|
||||
void write_ext(byte[] foo, int s, int l) throws IOException {
|
||||
try{
|
||||
io.put_ext(foo, s, l);
|
||||
}catch(NullPointerException e){}
|
||||
}
|
||||
|
||||
void eof_remote(){
|
||||
eof_remote=true;
|
||||
try{
|
||||
io.out_close();
|
||||
}
|
||||
catch(NullPointerException e){}
|
||||
}
|
||||
|
||||
void eof(){
|
||||
if(eof_local)return;
|
||||
eof_local=true;
|
||||
|
||||
int i = getRecipient();
|
||||
if(i == -1) return;
|
||||
|
||||
try{
|
||||
Buffer buf=new Buffer(100);
|
||||
Packet packet=new Packet(buf);
|
||||
packet.reset();
|
||||
buf.putByte((byte)Session.SSH_MSG_CHANNEL_EOF);
|
||||
buf.putInt(i);
|
||||
synchronized(this){
|
||||
if(!close)
|
||||
getSession().write(packet);
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
//System.err.println("Channel.eof");
|
||||
//e.printStackTrace();
|
||||
}
|
||||
/*
|
||||
if(!isConnected()){ disconnect(); }
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
http://www1.ietf.org/internet-drafts/draft-ietf-secsh-connect-24.txt
|
||||
|
||||
5.3 Closing a Channel
|
||||
When a party will no longer send more data to a channel, it SHOULD
|
||||
send SSH_MSG_CHANNEL_EOF.
|
||||
|
||||
byte SSH_MSG_CHANNEL_EOF
|
||||
uint32 recipient_channel
|
||||
|
||||
No explicit response is sent to this message. However, the
|
||||
application may send EOF to whatever is at the other end of the
|
||||
channel. Note that the channel remains open after this message, and
|
||||
more data may still be sent in the other direction. This message
|
||||
does not consume window space and can be sent even if no window space
|
||||
is available.
|
||||
|
||||
When either party wishes to terminate the channel, it sends
|
||||
SSH_MSG_CHANNEL_CLOSE. Upon receiving this message, a party MUST
|
||||
send back a SSH_MSG_CHANNEL_CLOSE unless it has already sent this
|
||||
message for the channel. The channel is considered closed for a
|
||||
party when it has both sent and received SSH_MSG_CHANNEL_CLOSE, and
|
||||
the party may then reuse the channel number. A party MAY send
|
||||
SSH_MSG_CHANNEL_CLOSE without having sent or received
|
||||
SSH_MSG_CHANNEL_EOF.
|
||||
|
||||
byte SSH_MSG_CHANNEL_CLOSE
|
||||
uint32 recipient_channel
|
||||
|
||||
This message does not consume window space and can be sent even if no
|
||||
window space is available.
|
||||
|
||||
It is recommended that any data sent before this message is delivered
|
||||
to the actual destination, if possible.
|
||||
*/
|
||||
|
||||
void close(){
|
||||
if(close)return;
|
||||
close=true;
|
||||
eof_local=eof_remote=true;
|
||||
|
||||
int i = getRecipient();
|
||||
if(i == -1) return;
|
||||
|
||||
try{
|
||||
Buffer buf=new Buffer(100);
|
||||
Packet packet=new Packet(buf);
|
||||
packet.reset();
|
||||
buf.putByte((byte)Session.SSH_MSG_CHANNEL_CLOSE);
|
||||
buf.putInt(i);
|
||||
synchronized(this){
|
||||
getSession().write(packet);
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public boolean isClosed(){
|
||||
return close;
|
||||
}
|
||||
static void disconnect(Session session){
|
||||
Channel[] channels=null;
|
||||
int count=0;
|
||||
synchronized(pool){
|
||||
channels=new Channel[pool.size()];
|
||||
for(int i=0; i<pool.size(); i++){
|
||||
try{
|
||||
Channel c=pool.elementAt(i);
|
||||
if(c.session==session){
|
||||
channels[count++]=c;
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int i=0; i<count; i++){
|
||||
channels[i].disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect(){
|
||||
//System.err.println(this+":disconnect "+io+" "+connected);
|
||||
//Thread.dumpStack();
|
||||
|
||||
try{
|
||||
|
||||
synchronized(this){
|
||||
if(!connected){
|
||||
return;
|
||||
}
|
||||
connected=false;
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
eof_remote=eof_local=true;
|
||||
|
||||
thread=null;
|
||||
|
||||
try{
|
||||
if(io!=null){
|
||||
io.close();
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
//e.printStackTrace();
|
||||
}
|
||||
// io=null;
|
||||
}
|
||||
finally{
|
||||
Channel.del(this);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected(){
|
||||
Session _session=this.session;
|
||||
if(_session!=null){
|
||||
return _session.isConnected() && connected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sendSignal(String signal) throws Exception {
|
||||
RequestSignal request=new RequestSignal();
|
||||
request.setSignal(signal);
|
||||
request.request(getSession(), this);
|
||||
}
|
||||
|
||||
// public String toString(){
|
||||
// return "Channel: type="+new String(type)+",id="+id+",recipient="+recipient+",window_size="+window_size+",packet_size="+packet_size;
|
||||
// }
|
||||
|
||||
/*
|
||||
class OutputThread extends Thread{
|
||||
Channel c;
|
||||
OutputThread(Channel c){ this.c=c;}
|
||||
public void run(){c.output_thread();}
|
||||
}
|
||||
*/
|
||||
|
||||
static class PassiveInputStream extends MyPipedInputStream{
|
||||
PipedOutputStream os;
|
||||
PassiveInputStream(PipedOutputStream out, int size) throws IOException{
|
||||
super(out, size);
|
||||
this.os=out;
|
||||
}
|
||||
PassiveInputStream(PipedOutputStream out) throws IOException{
|
||||
super(out);
|
||||
this.os=out;
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException{
|
||||
if(this.os!=null){
|
||||
this.os.close();
|
||||
}
|
||||
this.os=null;
|
||||
}
|
||||
}
|
||||
static class PassiveOutputStream extends PipedOutputStream{
|
||||
private MyPipedInputStream _sink=null;
|
||||
PassiveOutputStream(PipedInputStream in,
|
||||
boolean resizable_buffer) throws IOException{
|
||||
super(in);
|
||||
if(resizable_buffer && (in instanceof MyPipedInputStream)) {
|
||||
this._sink=(MyPipedInputStream)in;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
if(_sink != null) {
|
||||
_sink.checkSpace(1);
|
||||
}
|
||||
super.write(b);
|
||||
}
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if(_sink != null) {
|
||||
_sink.checkSpace(len);
|
||||
}
|
||||
super.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
void setExitStatus(int status){ exitstatus=status; }
|
||||
public int getExitStatus(){ return exitstatus; }
|
||||
|
||||
void setSession(Session session){
|
||||
this.session=session;
|
||||
}
|
||||
|
||||
public Session getSession() throws JSchException{
|
||||
Session _session=session;
|
||||
if(_session==null){
|
||||
throw new JSchException("session is not available");
|
||||
}
|
||||
return _session;
|
||||
}
|
||||
public int getId(){ return id; }
|
||||
|
||||
protected void sendOpenConfirmation() throws Exception{
|
||||
Buffer buf=new Buffer(200);
|
||||
Packet packet=new Packet(buf);
|
||||
packet.reset();
|
||||
buf.putByte((byte)SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
|
||||
buf.putInt(getRecipient());
|
||||
buf.putInt(id);
|
||||
buf.putInt(lwsize);
|
||||
buf.putInt(lmpsize);
|
||||
getSession().write(packet);
|
||||
}
|
||||
|
||||
protected void sendOpenFailure(int reasoncode){
|
||||
try{
|
||||
Buffer buf=new Buffer(200);
|
||||
Packet packet=new Packet(buf);
|
||||
packet.reset();
|
||||
buf.putByte((byte)SSH_MSG_CHANNEL_OPEN_FAILURE);
|
||||
buf.putInt(getRecipient());
|
||||
buf.putInt(reasoncode);
|
||||
buf.putString(Util.str2byte("open failed"));
|
||||
buf.putString(Util.empty);
|
||||
getSession().write(packet);
|
||||
}
|
||||
catch(Exception e){
|
||||
}
|
||||
}
|
||||
|
||||
protected Packet genChannelOpenPacket(){
|
||||
Buffer buf=new Buffer(200);
|
||||
Packet packet=new Packet(buf);
|
||||
// byte SSH_MSG_CHANNEL_OPEN(90)
|
||||
// string channel type //
|
||||
// uint32 sender channel // 0
|
||||
// uint32 initial window size // 0x100000(65536)
|
||||
// uint32 maxmum packet size // 0x4000(16384)
|
||||
packet.reset();
|
||||
buf.putByte((byte)90);
|
||||
buf.putString(this.type);
|
||||
buf.putInt(this.id);
|
||||
buf.putInt(this.lwsize);
|
||||
buf.putInt(this.lmpsize);
|
||||
return packet;
|
||||
}
|
||||
|
||||
protected void sendChannelOpen() throws Exception {
|
||||
Session _session=getSession();
|
||||
if(!_session.isConnected()){
|
||||
throw new JSchException("session is down");
|
||||
}
|
||||
|
||||
Packet packet = genChannelOpenPacket();
|
||||
_session.write(packet);
|
||||
|
||||
int retry=2000;
|
||||
long start=System.currentTimeMillis();
|
||||
long timeout=connectTimeout;
|
||||
if(timeout!=0L) retry = 1;
|
||||
synchronized(this){
|
||||
while(this.getRecipient()==-1 &&
|
||||
_session.isConnected() &&
|
||||
retry>0){
|
||||
if(timeout>0L){
|
||||
if((System.currentTimeMillis()-start)>timeout){
|
||||
retry=0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try{
|
||||
long t = timeout==0L ? 10L : timeout;
|
||||
this.notifyme=1;
|
||||
wait(t);
|
||||
}
|
||||
catch(InterruptedException e){
|
||||
}
|
||||
finally{
|
||||
this.notifyme=0;
|
||||
}
|
||||
retry--;
|
||||
}
|
||||
}
|
||||
if(!_session.isConnected()){
|
||||
throw new JSchException("session is down");
|
||||
}
|
||||
if(this.getRecipient()==-1){ // timeout
|
||||
throw new JSchException("channel is not opened.");
|
||||
}
|
||||
if(this.open_confirmation==false){ // SSH_MSG_CHANNEL_OPEN_FAILURE
|
||||
throw new JSchException("channel is not opened.");
|
||||
}
|
||||
connected=true;
|
||||
}
|
||||
}
|
@@ -1,287 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2006-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Vector;
|
||||
|
||||
class ChannelAgentForwarding extends Channel{
|
||||
|
||||
static private final int LOCAL_WINDOW_SIZE_MAX=0x20000;
|
||||
static private final int LOCAL_MAXIMUM_PACKET_SIZE=0x4000;
|
||||
|
||||
static private final byte SSH_AGENTC_REQUEST_RSA_IDENTITIES = 1;
|
||||
static private final byte SSH_AGENT_RSA_IDENTITIES_ANSWER = 2;
|
||||
static private final byte SSH_AGENTC_RSA_CHALLENGE = 3;
|
||||
static private final byte SSH_AGENT_RSA_RESPONSE = 4;
|
||||
static private final byte SSH_AGENT_FAILURE = 5;
|
||||
static private final byte SSH_AGENT_SUCCESS = 6;
|
||||
static private final byte SSH_AGENTC_ADD_RSA_IDENTITY = 7;
|
||||
static private final byte SSH_AGENTC_REMOVE_RSA_IDENTITY = 8;
|
||||
static private final byte SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9;
|
||||
|
||||
static private final byte SSH2_AGENTC_REQUEST_IDENTITIES=11;
|
||||
static private final byte SSH2_AGENT_IDENTITIES_ANSWER=12;
|
||||
static private final byte SSH2_AGENTC_SIGN_REQUEST=13;
|
||||
static private final byte SSH2_AGENT_SIGN_RESPONSE=14;
|
||||
static private final byte SSH2_AGENTC_ADD_IDENTITY=17;
|
||||
static private final byte SSH2_AGENTC_REMOVE_IDENTITY=18;
|
||||
static private final byte SSH2_AGENTC_REMOVE_ALL_IDENTITIES=19;
|
||||
static private final byte SSH2_AGENT_FAILURE=30;
|
||||
|
||||
//static private final int SSH_AGENT_OLD_SIGNATURE=0x1;
|
||||
static private final int SSH_AGENT_RSA_SHA2_256=0x2;
|
||||
static private final int SSH_AGENT_RSA_SHA2_512=0x4;
|
||||
|
||||
private Buffer rbuf=null;
|
||||
private Buffer wbuf=null;
|
||||
private Packet packet=null;
|
||||
private Buffer mbuf=null;
|
||||
|
||||
ChannelAgentForwarding(){
|
||||
super();
|
||||
|
||||
setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE);
|
||||
|
||||
type=Util.str2byte("auth-agent@openssh.com");
|
||||
rbuf=new Buffer();
|
||||
rbuf.reset();
|
||||
//wbuf=new Buffer(rmpsize);
|
||||
//packet=new Packet(wbuf);
|
||||
mbuf=new Buffer();
|
||||
connected=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void run(){
|
||||
try{
|
||||
sendOpenConfirmation();
|
||||
}
|
||||
catch(Exception e){
|
||||
close=true;
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(byte[] foo, int s, int l) throws IOException {
|
||||
|
||||
if(packet==null){
|
||||
wbuf=new Buffer(rmpsize);
|
||||
packet=new Packet(wbuf);
|
||||
}
|
||||
|
||||
rbuf.shift();
|
||||
if(rbuf.buffer.length<rbuf.index+l){
|
||||
byte[] newbuf=new byte[rbuf.s+l];
|
||||
System.arraycopy(rbuf.buffer, 0, newbuf, 0, rbuf.buffer.length);
|
||||
rbuf.buffer=newbuf;
|
||||
}
|
||||
|
||||
rbuf.putByte(foo, s, l);
|
||||
|
||||
int mlen=rbuf.getInt();
|
||||
if(mlen>rbuf.getLength()){
|
||||
rbuf.s-=4;
|
||||
return;
|
||||
}
|
||||
|
||||
int typ=rbuf.getByte();
|
||||
|
||||
Session _session=null;
|
||||
try{
|
||||
_session=getSession();
|
||||
}
|
||||
catch(JSchException e){
|
||||
throw new IOException(e.toString(), e);
|
||||
}
|
||||
|
||||
IdentityRepository irepo = _session.getIdentityRepository();
|
||||
UserInfo userinfo=_session.getUserInfo();
|
||||
|
||||
mbuf.reset();
|
||||
|
||||
if(typ==SSH2_AGENTC_REQUEST_IDENTITIES){
|
||||
mbuf.putByte(SSH2_AGENT_IDENTITIES_ANSWER);
|
||||
Vector<Identity> identities = irepo.getIdentities();
|
||||
synchronized(identities){
|
||||
int count=0;
|
||||
for(int i=0; i<identities.size(); i++){
|
||||
Identity identity=identities.elementAt(i);
|
||||
if(identity.getPublicKeyBlob()!=null)
|
||||
count++;
|
||||
}
|
||||
mbuf.putInt(count);
|
||||
for(int i=0; i<identities.size(); i++){
|
||||
Identity identity=identities.elementAt(i);
|
||||
byte[] pubkeyblob=identity.getPublicKeyBlob();
|
||||
if(pubkeyblob==null)
|
||||
continue;
|
||||
mbuf.putString(pubkeyblob);
|
||||
mbuf.putString(Util.empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(typ==SSH_AGENTC_REQUEST_RSA_IDENTITIES) {
|
||||
mbuf.putByte(SSH_AGENT_RSA_IDENTITIES_ANSWER);
|
||||
mbuf.putInt(0);
|
||||
}
|
||||
else if(typ==SSH2_AGENTC_SIGN_REQUEST){
|
||||
byte[] blob=rbuf.getString();
|
||||
byte[] data=rbuf.getString();
|
||||
int flags=rbuf.getInt();
|
||||
|
||||
// if((flags & SSH_AGENT_OLD_SIGNATURE)!=0){ // old OpenSSH 2.0, 2.1
|
||||
// datafellows = SSH_BUG_SIGBLOB;
|
||||
// }
|
||||
|
||||
Vector<Identity> identities = irepo.getIdentities();
|
||||
Identity identity = null;
|
||||
synchronized(identities){
|
||||
for(int i=0; i<identities.size(); i++){
|
||||
Identity _identity=identities.elementAt(i);
|
||||
if(_identity.getPublicKeyBlob()==null)
|
||||
continue;
|
||||
if(!Util.array_equals(blob, _identity.getPublicKeyBlob())){
|
||||
continue;
|
||||
}
|
||||
if(_identity.isEncrypted()){
|
||||
if(userinfo==null)
|
||||
continue;
|
||||
while(_identity.isEncrypted()){
|
||||
if(!userinfo.promptPassphrase("Passphrase for "+_identity.getName())){
|
||||
break;
|
||||
}
|
||||
|
||||
String _passphrase=userinfo.getPassphrase();
|
||||
if(_passphrase==null){
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] passphrase=Util.str2byte(_passphrase);
|
||||
try{
|
||||
if(_identity.setPassphrase(passphrase)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(JSchException e){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!_identity.isEncrypted()){
|
||||
identity=_identity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] signature=null;
|
||||
|
||||
if(identity!=null){
|
||||
Buffer kbuf=new Buffer(blob);
|
||||
String keytype=Util.byte2str(kbuf.getString());
|
||||
if(keytype.equals("ssh-rsa")){
|
||||
if((flags & SSH_AGENT_RSA_SHA2_256)!=0){
|
||||
signature=identity.getSignature(data, "rsa-sha2-256");
|
||||
}
|
||||
else if((flags & SSH_AGENT_RSA_SHA2_512)!=0){
|
||||
signature=identity.getSignature(data, "rsa-sha2-512");
|
||||
}
|
||||
else{
|
||||
signature=identity.getSignature(data, "ssh-rsa");
|
||||
}
|
||||
}
|
||||
else{
|
||||
signature=identity.getSignature(data);
|
||||
}
|
||||
}
|
||||
|
||||
if(signature==null){
|
||||
mbuf.putByte(SSH2_AGENT_FAILURE);
|
||||
}
|
||||
else{
|
||||
mbuf.putByte(SSH2_AGENT_SIGN_RESPONSE);
|
||||
mbuf.putString(signature);
|
||||
}
|
||||
}
|
||||
else if(typ==SSH2_AGENTC_REMOVE_IDENTITY){
|
||||
byte[] blob=rbuf.getString();
|
||||
irepo.remove(blob);
|
||||
mbuf.putByte(SSH_AGENT_SUCCESS);
|
||||
}
|
||||
else if(typ==SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES){
|
||||
mbuf.putByte(SSH_AGENT_SUCCESS);
|
||||
}
|
||||
else if(typ==SSH2_AGENTC_REMOVE_ALL_IDENTITIES){
|
||||
irepo.removeAll();
|
||||
mbuf.putByte(SSH_AGENT_SUCCESS);
|
||||
}
|
||||
else if(typ==SSH2_AGENTC_ADD_IDENTITY){
|
||||
int fooo = rbuf.getLength();
|
||||
byte[] tmp = new byte[fooo];
|
||||
rbuf.getByte(tmp);
|
||||
boolean result = irepo.add(tmp);
|
||||
mbuf.putByte(result ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
|
||||
}
|
||||
else {
|
||||
rbuf.skip(rbuf.getLength()-1);
|
||||
mbuf.putByte(SSH_AGENT_FAILURE);
|
||||
}
|
||||
|
||||
byte[] response = new byte[mbuf.getLength()];
|
||||
mbuf.getByte(response);
|
||||
send(response);
|
||||
}
|
||||
|
||||
private void send(byte[] message){
|
||||
packet.reset();
|
||||
wbuf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
|
||||
wbuf.putInt(recipient);
|
||||
wbuf.putInt(4+message.length);
|
||||
wbuf.putString(message);
|
||||
|
||||
try{
|
||||
getSession().write(packet, this, 4+message.length);
|
||||
}
|
||||
catch(Exception e){
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void eof_remote(){
|
||||
super.eof_remote();
|
||||
eof();
|
||||
}
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import static com.jcraft.jsch.Session.SSH_MSG_CHANNEL_OPEN;
|
||||
|
||||
/**
|
||||
* Extension of {@link ChannelDirectTCPIP} to support socket forwarding.
|
||||
* <p>
|
||||
* https://raw.githubusercontent.com/openssh/openssh-portable/master/PROTOCOL
|
||||
*/
|
||||
public class ChannelDirectStreamLocal extends ChannelDirectTCPIP {
|
||||
|
||||
static private final int LOCAL_WINDOW_SIZE_MAX = 0x20000;
|
||||
static private final int LOCAL_MAXIMUM_PACKET_SIZE = 0x4000;
|
||||
static private final byte[] _type = Util.str2byte("direct-streamlocal@openssh.com");
|
||||
|
||||
private String socketPath;
|
||||
|
||||
ChannelDirectStreamLocal() {
|
||||
super();
|
||||
type = _type;
|
||||
setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Packet genChannelOpenPacket() {
|
||||
|
||||
if (socketPath == null) {
|
||||
session.getLogger().log(Logger.FATAL, "socketPath must be set");
|
||||
throw new RuntimeException("socketPath must be set");
|
||||
}
|
||||
|
||||
/*
|
||||
Similar to direct-tcpip, direct-streamlocal is sent by the client
|
||||
to request that the server make a connection to a Unix domain socket.
|
||||
|
||||
byte SSH_MSG_CHANNEL_OPEN
|
||||
string "direct-streamlocal@openssh.com"
|
||||
uint32 sender channel
|
||||
uint32 initial window size
|
||||
uint32 maximum packet size
|
||||
string socket path
|
||||
string reserved
|
||||
uint32 reserved
|
||||
*/
|
||||
|
||||
Buffer buf = new Buffer(50 +
|
||||
socketPath.length() +
|
||||
Session.buffer_margin);
|
||||
Packet packet = new Packet(buf);
|
||||
packet.reset();
|
||||
buf.putByte((byte) SSH_MSG_CHANNEL_OPEN);
|
||||
buf.putString(this.type);
|
||||
buf.putInt(id);
|
||||
buf.putInt(lwsize);
|
||||
buf.putInt(lmpsize);
|
||||
buf.putString(Util.str2byte(socketPath));
|
||||
buf.putString(Util.str2byte(originator_IP_address));
|
||||
buf.putInt(originator_port);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public String getSocketPath() {
|
||||
return socketPath;
|
||||
}
|
||||
|
||||
public void setSocketPath(String socketPath) {
|
||||
this.socketPath = socketPath;
|
||||
}
|
||||
}
|
@@ -1,176 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class ChannelDirectTCPIP extends Channel{
|
||||
|
||||
static private final int LOCAL_WINDOW_SIZE_MAX=0x20000;
|
||||
static private final int LOCAL_MAXIMUM_PACKET_SIZE=0x4000;
|
||||
static private final byte[] _type = Util.str2byte("direct-tcpip");
|
||||
String host;
|
||||
int port;
|
||||
|
||||
String originator_IP_address="127.0.0.1";
|
||||
int originator_port=0;
|
||||
|
||||
ChannelDirectTCPIP(){
|
||||
super();
|
||||
type = _type;
|
||||
setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
void init (){
|
||||
io=new IO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(int connectTimeout) throws JSchException{
|
||||
this.connectTimeout=connectTimeout;
|
||||
try{
|
||||
Session _session=getSession();
|
||||
if(!_session.isConnected()){
|
||||
throw new JSchException("session is down");
|
||||
}
|
||||
|
||||
if(io.in!=null){
|
||||
thread=new Thread(this::run);
|
||||
thread.setName("DirectTCPIP thread "+_session.getHost());
|
||||
if(_session.daemon_thread){
|
||||
thread.setDaemon(_session.daemon_thread);
|
||||
}
|
||||
thread.start();
|
||||
}
|
||||
else {
|
||||
sendChannelOpen();
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
io.close();
|
||||
io=null;
|
||||
Channel.del(this);
|
||||
if (e instanceof JSchException) {
|
||||
throw (JSchException) e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void run(){
|
||||
|
||||
try{
|
||||
sendChannelOpen();
|
||||
|
||||
Buffer buf=new Buffer(rmpsize);
|
||||
Packet packet=new Packet(buf);
|
||||
Session _session=getSession();
|
||||
int i=0;
|
||||
|
||||
while(isConnected() &&
|
||||
thread!=null &&
|
||||
io!=null &&
|
||||
io.in!=null){
|
||||
i=io.in.read(buf.buffer,
|
||||
14,
|
||||
buf.buffer.length-14
|
||||
-Session.buffer_margin
|
||||
);
|
||||
if(i<=0){
|
||||
eof();
|
||||
break;
|
||||
}
|
||||
packet.reset();
|
||||
buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
|
||||
buf.putInt(recipient);
|
||||
buf.putInt(i);
|
||||
buf.skip(i);
|
||||
synchronized(this){
|
||||
if(close)
|
||||
break;
|
||||
_session.write(packet, this, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
// Whenever an exception is thrown by sendChannelOpen(),
|
||||
// 'connected' is false.
|
||||
if(!connected){
|
||||
connected=true;
|
||||
}
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
eof();
|
||||
disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputStream(InputStream in){
|
||||
io.setInputStream(in);
|
||||
}
|
||||
@Override
|
||||
public void setOutputStream(OutputStream out){
|
||||
io.setOutputStream(out);
|
||||
}
|
||||
|
||||
public void setHost(String host){this.host=host;}
|
||||
public void setPort(int port){this.port=port;}
|
||||
public void setOrgIPAddress(String foo){this.originator_IP_address=foo;}
|
||||
public void setOrgPort(int foo){this.originator_port=foo;}
|
||||
|
||||
@Override
|
||||
protected Packet genChannelOpenPacket(){
|
||||
Buffer buf = new Buffer(50 + // 6 + 4*8 + 12
|
||||
host.length() + originator_IP_address.length() +
|
||||
Session.buffer_margin);
|
||||
Packet packet = new Packet(buf);
|
||||
// byte SSH_MSG_CHANNEL_OPEN(90)
|
||||
// string channel type //
|
||||
// uint32 sender channel // 0
|
||||
// uint32 initial window size // 0x100000(65536)
|
||||
// uint32 maxmum packet size // 0x4000(16384)
|
||||
packet.reset();
|
||||
buf.putByte((byte)90);
|
||||
buf.putString(this.type);
|
||||
buf.putInt(id);
|
||||
buf.putInt(lwsize);
|
||||
buf.putInt(lmpsize);
|
||||
buf.putString(Util.str2byte(host));
|
||||
buf.putInt(port);
|
||||
buf.putString(Util.str2byte(originator_IP_address));
|
||||
buf.putInt(originator_port);
|
||||
return packet;
|
||||
}
|
||||
}
|
@@ -1,84 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class ChannelExec extends ChannelSession{
|
||||
|
||||
byte[] command=new byte[0];
|
||||
|
||||
@Override
|
||||
public void start() throws JSchException{
|
||||
Session _session=getSession();
|
||||
try{
|
||||
sendRequests();
|
||||
Request request=new RequestExec(command);
|
||||
request.request(_session, this);
|
||||
}
|
||||
catch(Exception e){
|
||||
if(e instanceof JSchException) throw (JSchException)e;
|
||||
throw new JSchException("ChannelExec", e);
|
||||
}
|
||||
|
||||
if(io.in!=null){
|
||||
thread=new Thread(this::run);
|
||||
thread.setName("Exec thread "+_session.getHost());
|
||||
if(_session.daemon_thread){
|
||||
thread.setDaemon(_session.daemon_thread);
|
||||
}
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCommand(String command){
|
||||
this.command=Util.str2byte(command);
|
||||
}
|
||||
public void setCommand(byte[] command){
|
||||
this.command=command;
|
||||
}
|
||||
|
||||
@Override
|
||||
void init() throws JSchException {
|
||||
io.setInputStream(getSession().in);
|
||||
io.setOutputStream(getSession().out);
|
||||
}
|
||||
|
||||
public void setErrStream(OutputStream out){
|
||||
setExtOutputStream(out);
|
||||
}
|
||||
public void setErrStream(OutputStream out, boolean dontclose){
|
||||
setExtOutputStream(out, dontclose);
|
||||
}
|
||||
public InputStream getErrStream() throws IOException {
|
||||
return getExtInputStream();
|
||||
}
|
||||
}
|
@@ -1,335 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.util.Vector;
|
||||
|
||||
public class ChannelForwardedTCPIP extends Channel{
|
||||
|
||||
private static Vector<Config> pool = new Vector<>();
|
||||
|
||||
static private final int LOCAL_WINDOW_SIZE_MAX=0x20000;
|
||||
//static private final int LOCAL_WINDOW_SIZE_MAX=0x100000;
|
||||
static private final int LOCAL_MAXIMUM_PACKET_SIZE=0x4000;
|
||||
|
||||
static private final int TIMEOUT=10*1000;
|
||||
|
||||
private Socket socket=null;
|
||||
private ForwardedTCPIPDaemon daemon=null;
|
||||
private Config config = null;
|
||||
|
||||
ChannelForwardedTCPIP(){
|
||||
super();
|
||||
setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE);
|
||||
io=new IO();
|
||||
connected=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(){
|
||||
try{
|
||||
if(config instanceof ConfigDaemon){
|
||||
ConfigDaemon _config = (ConfigDaemon)config;
|
||||
Class<? extends ForwardedTCPIPDaemon> c=Class.forName(_config.target).asSubclass(ForwardedTCPIPDaemon.class);
|
||||
daemon=c.getDeclaredConstructor().newInstance();
|
||||
|
||||
PipedOutputStream out=new PipedOutputStream();
|
||||
io.setInputStream(new PassiveInputStream(out
|
||||
, 32*1024
|
||||
), false);
|
||||
|
||||
daemon.setChannel(this, getInputStream(), out);
|
||||
daemon.setArg(_config.arg);
|
||||
new Thread(daemon).start();
|
||||
}
|
||||
else{
|
||||
ConfigLHost _config = (ConfigLHost)config;
|
||||
socket=(_config.factory==null) ?
|
||||
Util.createSocket(_config.target, _config.lport, TIMEOUT) :
|
||||
_config.factory.createSocket(_config.target, _config.lport);
|
||||
socket.setTcpNoDelay(true);
|
||||
io.setInputStream(socket.getInputStream());
|
||||
io.setOutputStream(socket.getOutputStream());
|
||||
}
|
||||
sendOpenConfirmation();
|
||||
}
|
||||
catch(Exception e){
|
||||
sendOpenFailure(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED);
|
||||
close=true;
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
thread=Thread.currentThread();
|
||||
Buffer buf=new Buffer(rmpsize);
|
||||
Packet packet=new Packet(buf);
|
||||
int i=0;
|
||||
try{
|
||||
Session _session = getSession();
|
||||
while(thread!=null &&
|
||||
io!=null &&
|
||||
io.in!=null){
|
||||
i=io.in.read(buf.buffer,
|
||||
14,
|
||||
buf.buffer.length-14
|
||||
-Session.buffer_margin
|
||||
);
|
||||
if(i<=0){
|
||||
eof();
|
||||
break;
|
||||
}
|
||||
packet.reset();
|
||||
buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
|
||||
buf.putInt(recipient);
|
||||
buf.putInt(i);
|
||||
buf.skip(i);
|
||||
synchronized(this){
|
||||
if(close)
|
||||
break;
|
||||
_session.write(packet, this, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
//System.err.println(e);
|
||||
}
|
||||
//thread=null;
|
||||
//eof();
|
||||
disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
void getData(Buffer buf){
|
||||
setRecipient(buf.getInt());
|
||||
setRemoteWindowSize(buf.getUInt());
|
||||
setRemotePacketSize(buf.getInt());
|
||||
byte[] addr=buf.getString();
|
||||
int port=buf.getInt();
|
||||
byte[] orgaddr=buf.getString();
|
||||
int orgport=buf.getInt();
|
||||
|
||||
/*
|
||||
System.err.println("addr: "+Util.byte2str(addr));
|
||||
System.err.println("port: "+port);
|
||||
System.err.println("orgaddr: "+Util.byte2str(orgaddr));
|
||||
System.err.println("orgport: "+orgport);
|
||||
*/
|
||||
|
||||
Session _session=null;
|
||||
try{
|
||||
_session=getSession();
|
||||
}
|
||||
catch(JSchException e){
|
||||
// session has been already down.
|
||||
}
|
||||
|
||||
this.config = getPort(_session, Util.byte2str(addr), port);
|
||||
if(this.config == null)
|
||||
this.config = getPort(_session, null, port);
|
||||
|
||||
if(this.config == null){
|
||||
if(_session.getLogger().isEnabled(Logger.ERROR)){
|
||||
_session.getLogger().log(Logger.ERROR,
|
||||
"ChannelForwardedTCPIP: "+Util.byte2str(addr)+":"+port+" is not registered.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Config getPort(Session session, String address_to_bind, int rport){
|
||||
synchronized(pool){
|
||||
for(int i=0; i<pool.size(); i++){
|
||||
Config bar = pool.elementAt(i);
|
||||
if(bar.session != session) continue;
|
||||
if(bar.rport != rport) {
|
||||
if(bar.rport != 0 || bar.allocated_rport != rport)
|
||||
continue;
|
||||
}
|
||||
if(address_to_bind != null &&
|
||||
!bar.address_to_bind.equals(address_to_bind)) continue;
|
||||
return bar;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String[] getPortForwarding(Session session){
|
||||
Vector<String> foo = new Vector<>();
|
||||
synchronized(pool){
|
||||
for(int i=0; i<pool.size(); i++){
|
||||
Config config = pool.elementAt(i);
|
||||
if(config.session==session){
|
||||
if(config instanceof ConfigDaemon)
|
||||
foo.addElement(config.allocated_rport+":"+config.target+":");
|
||||
else
|
||||
foo.addElement(config.allocated_rport+":"+config.target+":"+((ConfigLHost)config).lport);
|
||||
}
|
||||
}
|
||||
}
|
||||
String[] bar=new String[foo.size()];
|
||||
for(int i=0; i<foo.size(); i++){
|
||||
bar[i]=foo.elementAt(i);
|
||||
}
|
||||
return bar;
|
||||
}
|
||||
|
||||
static String normalize(String address){
|
||||
if(address==null){ return "localhost"; }
|
||||
else if(address.length()==0 || address.equals("*")){ return ""; }
|
||||
else{ return address; }
|
||||
}
|
||||
|
||||
static void addPort(Session session, String _address_to_bind,
|
||||
int port, int allocated_port, String target, int lport, SocketFactory factory) throws JSchException{
|
||||
String address_to_bind=normalize(_address_to_bind);
|
||||
synchronized(pool){
|
||||
if(getPort(session, address_to_bind, port)!=null){
|
||||
throw new JSchException("PortForwardingR: remote port "+port+" is already registered.");
|
||||
}
|
||||
ConfigLHost config = new ConfigLHost();
|
||||
config.session = session;
|
||||
config.rport = port;
|
||||
config.allocated_rport = allocated_port;
|
||||
config.target = target;
|
||||
config.lport =lport;
|
||||
config.address_to_bind = address_to_bind;
|
||||
config.factory = factory;
|
||||
pool.addElement(config);
|
||||
}
|
||||
}
|
||||
static void addPort(Session session, String _address_to_bind,
|
||||
int port, int allocated_port, String daemon, Object[] arg) throws JSchException{
|
||||
String address_to_bind=normalize(_address_to_bind);
|
||||
synchronized(pool){
|
||||
if(getPort(session, address_to_bind, port)!=null){
|
||||
throw new JSchException("PortForwardingR: remote port "+port+" is already registered.");
|
||||
}
|
||||
ConfigDaemon config = new ConfigDaemon();
|
||||
config.session = session;
|
||||
config.rport = port;
|
||||
config.allocated_rport = port;
|
||||
config.target = daemon;
|
||||
config.arg = arg;
|
||||
config.address_to_bind = address_to_bind;
|
||||
pool.addElement(config);
|
||||
}
|
||||
}
|
||||
static void delPort(ChannelForwardedTCPIP c){
|
||||
Session _session=null;
|
||||
try{
|
||||
_session=c.getSession();
|
||||
}
|
||||
catch(JSchException e){
|
||||
// session has been already down.
|
||||
}
|
||||
if(_session!=null && c.config!=null)
|
||||
delPort(_session, c.config.rport);
|
||||
}
|
||||
static void delPort(Session session, int rport){
|
||||
delPort(session, null, rport);
|
||||
}
|
||||
static void delPort(Session session, String address_to_bind, int rport){
|
||||
synchronized(pool){
|
||||
Config foo = getPort(session, normalize(address_to_bind), rport);
|
||||
if(foo == null)
|
||||
foo = getPort(session, null, rport);
|
||||
if(foo==null) return;
|
||||
pool.removeElement(foo);
|
||||
if(address_to_bind==null){
|
||||
address_to_bind=foo.address_to_bind;
|
||||
}
|
||||
if(address_to_bind==null){
|
||||
address_to_bind="0.0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
Buffer buf=new Buffer(200); // ??
|
||||
Packet packet=new Packet(buf);
|
||||
|
||||
try{
|
||||
// byte SSH_MSG_GLOBAL_REQUEST 80
|
||||
// string "cancel-tcpip-forward"
|
||||
// boolean want_reply
|
||||
// string address_to_bind (e.g. "127.0.0.1")
|
||||
// uint32 port number to bind
|
||||
packet.reset();
|
||||
buf.putByte((byte) 80/*SSH_MSG_GLOBAL_REQUEST*/);
|
||||
buf.putString(Util.str2byte("cancel-tcpip-forward"));
|
||||
buf.putByte((byte)0);
|
||||
buf.putString(Util.str2byte(address_to_bind));
|
||||
buf.putInt(rport);
|
||||
session.write(packet);
|
||||
}
|
||||
catch(Exception e){
|
||||
// throw new JSchException(e.toString(), e);
|
||||
}
|
||||
}
|
||||
static void delPort(Session session){
|
||||
int[] rport=null;
|
||||
int count=0;
|
||||
synchronized(pool){
|
||||
rport=new int[pool.size()];
|
||||
for(int i=0; i<pool.size(); i++){
|
||||
Config config = pool.elementAt(i);
|
||||
if(config.session == session) {
|
||||
rport[count++]=config.rport; // ((Integer)bar[1]).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int i=0; i<count; i++){
|
||||
delPort(session, rport[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public int getRemotePort(){return (config!=null ? config.rport: 0);}
|
||||
private void setSocketFactory(SocketFactory factory){
|
||||
if(config!=null && (config instanceof ConfigLHost) )
|
||||
((ConfigLHost)config).factory = factory;
|
||||
}
|
||||
static abstract class Config {
|
||||
Session session;
|
||||
int rport;
|
||||
int allocated_rport;
|
||||
String address_to_bind;
|
||||
String target;
|
||||
}
|
||||
|
||||
static class ConfigDaemon extends Config {
|
||||
Object[] arg;
|
||||
}
|
||||
|
||||
static class ConfigLHost extends Config {
|
||||
int lport;
|
||||
SocketFactory factory;
|
||||
}
|
||||
}
|
@@ -1,279 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
class ChannelSession extends Channel{
|
||||
private static byte[] _session=Util.str2byte("session");
|
||||
|
||||
protected boolean agent_forwarding=false;
|
||||
protected boolean xforwading=false;
|
||||
protected Hashtable<byte[], byte[]> env=null;
|
||||
|
||||
protected boolean pty=false;
|
||||
|
||||
protected String ttype="vt100";
|
||||
protected int tcol=80;
|
||||
protected int trow=24;
|
||||
protected int twp=640;
|
||||
protected int thp=480;
|
||||
protected byte[] terminal_mode=null;
|
||||
|
||||
ChannelSession(){
|
||||
super();
|
||||
type=_session;
|
||||
io=new IO();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the agent forwarding.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
public void setAgentForwarding(boolean enable){
|
||||
agent_forwarding=enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the X11 forwarding.
|
||||
* Refer to RFC4254 6.3.1. Requesting X11 Forwarding.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
@Override
|
||||
public void setXForwarding(boolean enable){
|
||||
xforwading=enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use #setEnv(String, String) or #setEnv(byte[], byte[]) instead.
|
||||
* @see #setEnv(String, String)
|
||||
* @see #setEnv(byte[], byte[])
|
||||
*/
|
||||
@Deprecated
|
||||
public void setEnv(Hashtable<byte[], byte[]> env){
|
||||
synchronized(this){
|
||||
this.env=env;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the environment variable.
|
||||
* If <code>name</code> and <code>value</code> are needed to be passed
|
||||
* to the remote in your favorite encoding,
|
||||
* use {@link #setEnv(byte[], byte[])}.
|
||||
* Refer to RFC4254 6.4 Environment Variable Passing.
|
||||
*
|
||||
* @param name A name for environment variable.
|
||||
* @param value A value for environment variable.
|
||||
*/
|
||||
public void setEnv(String name, String value){
|
||||
setEnv(Util.str2byte(name), Util.str2byte(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the environment variable.
|
||||
* Refer to RFC4254 6.4 Environment Variable Passing.
|
||||
*
|
||||
* @param name A name of environment variable.
|
||||
* @param value A value of environment variable.
|
||||
* @see #setEnv(String, String)
|
||||
*/
|
||||
public void setEnv(byte[] name, byte[] value){
|
||||
synchronized(this){
|
||||
getEnv().put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private Hashtable<byte[], byte[]> getEnv(){
|
||||
if(env==null)
|
||||
env=new Hashtable<>();
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a Pseudo-Terminal.
|
||||
* Refer to RFC4254 6.2. Requesting a Pseudo-Terminal.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
public void setPty(boolean enable){
|
||||
pty=enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the terminal mode.
|
||||
*
|
||||
* @param terminal_mode
|
||||
*/
|
||||
public void setTerminalMode(byte[] terminal_mode){
|
||||
this.terminal_mode=terminal_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the window dimension interactively.
|
||||
* Refer to RFC4254 6.7. Window Dimension Change Message.
|
||||
*
|
||||
* @param col terminal width, columns
|
||||
* @param row terminal height, rows
|
||||
* @param wp terminal width, pixels
|
||||
* @param hp terminal height, pixels
|
||||
*/
|
||||
public void setPtySize(int col, int row, int wp, int hp){
|
||||
setPtyType(this.ttype, col, row, wp, hp);
|
||||
if(!pty || !isConnected()){
|
||||
return;
|
||||
}
|
||||
try{
|
||||
RequestWindowChange request=new RequestWindowChange();
|
||||
request.setSize(col, row, wp, hp);
|
||||
request.request(getSession(), this);
|
||||
}
|
||||
catch(Exception e){
|
||||
//System.err.println("ChannelSessio.setPtySize: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the terminal type.
|
||||
* This method is not effective after Channel#connect().
|
||||
*
|
||||
* @param ttype terminal type(for example, "vt100")
|
||||
* @see #setPtyType(String, int, int, int, int)
|
||||
*/
|
||||
public void setPtyType(String ttype){
|
||||
setPtyType(ttype, 80, 24, 640, 480);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the terminal type.
|
||||
* This method is not effective after Channel#connect().
|
||||
*
|
||||
* @param ttype terminal type(for example, "vt100")
|
||||
* @param col terminal width, columns
|
||||
* @param row terminal height, rows
|
||||
* @param wp terminal width, pixels
|
||||
* @param hp terminal height, pixels
|
||||
*/
|
||||
public void setPtyType(String ttype, int col, int row, int wp, int hp){
|
||||
this.ttype=ttype;
|
||||
this.tcol=col;
|
||||
this.trow=row;
|
||||
this.twp=wp;
|
||||
this.thp=hp;
|
||||
}
|
||||
|
||||
protected void sendRequests() throws Exception{
|
||||
Session _session=getSession();
|
||||
Request request;
|
||||
if(agent_forwarding){
|
||||
request=new RequestAgentForwarding();
|
||||
request.request(_session, this);
|
||||
}
|
||||
|
||||
if(xforwading){
|
||||
request=new RequestX11();
|
||||
request.request(_session, this);
|
||||
}
|
||||
|
||||
if(pty){
|
||||
request=new RequestPtyReq();
|
||||
((RequestPtyReq)request).setTType(ttype);
|
||||
((RequestPtyReq)request).setTSize(tcol, trow, twp, thp);
|
||||
if(terminal_mode!=null){
|
||||
((RequestPtyReq)request).setTerminalMode(terminal_mode);
|
||||
}
|
||||
request.request(_session, this);
|
||||
}
|
||||
|
||||
if(env!=null){
|
||||
for(Enumeration<byte[]> _env=env.keys(); _env.hasMoreElements();){
|
||||
byte[] name=_env.nextElement();
|
||||
byte[] value=env.get(name);
|
||||
request=new RequestEnv();
|
||||
((RequestEnv)request).setEnv(toByteArray(name),
|
||||
toByteArray(value));
|
||||
request.request(_session, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] toByteArray(Object o){
|
||||
if(o instanceof String){
|
||||
return Util.str2byte((String)o);
|
||||
}
|
||||
return (byte[])o;
|
||||
}
|
||||
|
||||
@Override
|
||||
void run(){
|
||||
//System.err.println(this+":run >");
|
||||
|
||||
Buffer buf=new Buffer(rmpsize);
|
||||
Packet packet=new Packet(buf);
|
||||
int i=-1;
|
||||
try{
|
||||
while(isConnected() &&
|
||||
thread!=null &&
|
||||
io!=null &&
|
||||
io.in!=null){
|
||||
i=io.in.read(buf.buffer,
|
||||
14,
|
||||
buf.buffer.length-14
|
||||
-Session.buffer_margin
|
||||
);
|
||||
if(i==0)continue;
|
||||
if(i==-1){
|
||||
eof();
|
||||
break;
|
||||
}
|
||||
if(close)break;
|
||||
//System.out.println("write: "+i);
|
||||
packet.reset();
|
||||
buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
|
||||
buf.putInt(recipient);
|
||||
buf.putInt(i);
|
||||
buf.skip(i);
|
||||
getSession().write(packet, this, i);
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
//System.err.println("# ChannelExec.run");
|
||||
//e.printStackTrace();
|
||||
}
|
||||
Thread _thread=thread;
|
||||
if(_thread!=null){
|
||||
synchronized(_thread){ _thread.notifyAll(); }
|
||||
}
|
||||
thread=null;
|
||||
//System.err.println(this+":run <");
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ChannelShell extends ChannelSession{
|
||||
|
||||
ChannelShell(){
|
||||
super();
|
||||
pty=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws JSchException{
|
||||
Session _session=getSession();
|
||||
try{
|
||||
sendRequests();
|
||||
|
||||
Request request=new RequestShell();
|
||||
request.request(_session, this);
|
||||
}
|
||||
catch(Exception e){
|
||||
if(e instanceof JSchException) throw (JSchException)e;
|
||||
throw new JSchException("ChannelShell", e);
|
||||
}
|
||||
|
||||
if(io.in!=null){
|
||||
thread=new Thread(this::run);
|
||||
thread.setName("Shell for "+_session.host);
|
||||
if(_session.daemon_thread){
|
||||
thread.setDaemon(_session.daemon_thread);
|
||||
}
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void init() throws JSchException {
|
||||
io.setInputStream(getSession().in);
|
||||
io.setOutputStream(getSession().out);
|
||||
}
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2005-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class ChannelSubsystem extends ChannelSession{
|
||||
boolean want_reply=true;
|
||||
String subsystem="";
|
||||
public void setWantReply(boolean foo){ want_reply=foo; }
|
||||
public void setSubsystem(String foo){ subsystem=foo; }
|
||||
@Override
|
||||
public void start() throws JSchException{
|
||||
Session _session=getSession();
|
||||
try{
|
||||
Request request;
|
||||
if(xforwading){
|
||||
request=new RequestX11();
|
||||
request.request(_session, this);
|
||||
}
|
||||
if(pty){
|
||||
request=new RequestPtyReq();
|
||||
request.request(_session, this);
|
||||
}
|
||||
request=new RequestSubsystem();
|
||||
((RequestSubsystem)request).request(_session, this, subsystem, want_reply);
|
||||
}
|
||||
catch(Exception e){
|
||||
if(e instanceof JSchException){ throw (JSchException)e; }
|
||||
throw new JSchException("ChannelSubsystem", e);
|
||||
}
|
||||
if(io.in!=null){
|
||||
thread=new Thread(this::run);
|
||||
thread.setName("Subsystem for "+_session.host);
|
||||
if(_session.daemon_thread){
|
||||
thread.setDaemon(_session.daemon_thread);
|
||||
}
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void init() throws JSchException {
|
||||
io.setInputStream(getSession().in);
|
||||
io.setOutputStream(getSession().out);
|
||||
}
|
||||
|
||||
public void setErrStream(OutputStream out){
|
||||
setExtOutputStream(out);
|
||||
}
|
||||
public InputStream getErrStream() throws IOException {
|
||||
return getExtInputStream();
|
||||
}
|
||||
}
|
@@ -1,277 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.Hashtable;
|
||||
|
||||
class ChannelX11 extends Channel{
|
||||
|
||||
static private final int LOCAL_WINDOW_SIZE_MAX=0x20000;
|
||||
static private final int LOCAL_MAXIMUM_PACKET_SIZE=0x4000;
|
||||
|
||||
static private final int TIMEOUT=10*1000;
|
||||
|
||||
private static String host="127.0.0.1";
|
||||
private static int port=6000;
|
||||
|
||||
private boolean init=true;
|
||||
|
||||
static byte[] cookie=null;
|
||||
private static byte[] cookie_hex=null;
|
||||
|
||||
private static Hashtable<Session, byte[]> faked_cookie_pool=new Hashtable<>();
|
||||
private static Hashtable<Session, byte[]> faked_cookie_hex_pool=new Hashtable<>();
|
||||
|
||||
private static byte[] table={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,
|
||||
0x61,0x62,0x63,0x64,0x65,0x66};
|
||||
|
||||
private Socket socket = null;
|
||||
|
||||
static int revtable(byte foo){
|
||||
for(int i=0; i<table.length; i++){
|
||||
if(table[i]==foo)return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static void setCookie(String foo){
|
||||
cookie_hex=Util.str2byte(foo);
|
||||
cookie=new byte[16];
|
||||
for(int i=0; i<16; i++){
|
||||
cookie[i]=(byte)(((revtable(cookie_hex[i*2])<<4)&0xf0) |
|
||||
((revtable(cookie_hex[i*2+1]))&0xf));
|
||||
}
|
||||
}
|
||||
static void setHost(String foo){ host=foo; }
|
||||
static void setPort(int foo){ port=foo; }
|
||||
static byte[] getFakedCookie(Session session){
|
||||
synchronized(faked_cookie_hex_pool){
|
||||
byte[] foo=faked_cookie_hex_pool.get(session);
|
||||
if(foo==null){
|
||||
Random random=Session.random;
|
||||
foo=new byte[16];
|
||||
synchronized(random){
|
||||
random.fill(foo, 0, 16);
|
||||
}
|
||||
/*
|
||||
System.err.print("faked_cookie: ");
|
||||
for(int i=0; i<foo.length; i++){
|
||||
System.err.print(Integer.toHexString(foo[i]&0xff)+":");
|
||||
}
|
||||
System.err.println("");
|
||||
*/
|
||||
faked_cookie_pool.put(session, foo);
|
||||
byte[] bar=new byte[32];
|
||||
for(int i=0; i<16; i++){
|
||||
bar[2*i]=table[(foo[i]>>>4)&0xf];
|
||||
bar[2*i+1]=table[(foo[i])&0xf];
|
||||
}
|
||||
faked_cookie_hex_pool.put(session, bar);
|
||||
foo=bar;
|
||||
}
|
||||
return foo;
|
||||
}
|
||||
}
|
||||
|
||||
static void removeFakedCookie(Session session){
|
||||
synchronized(faked_cookie_hex_pool){
|
||||
faked_cookie_hex_pool.remove(session);
|
||||
faked_cookie_pool.remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
ChannelX11(){
|
||||
super();
|
||||
|
||||
setLocalWindowSizeMax(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalWindowSize(LOCAL_WINDOW_SIZE_MAX);
|
||||
setLocalPacketSize(LOCAL_MAXIMUM_PACKET_SIZE);
|
||||
|
||||
type=Util.str2byte("x11");
|
||||
|
||||
connected=true;
|
||||
/*
|
||||
try{
|
||||
socket=Util.createSocket(host, port, TIMEOUT);
|
||||
socket.setTcpNoDelay(true);
|
||||
io=new IO();
|
||||
io.setInputStream(socket.getInputStream());
|
||||
io.setOutputStream(socket.getOutputStream());
|
||||
}
|
||||
catch(Exception e){
|
||||
//System.err.println(e);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
void run(){
|
||||
|
||||
try{
|
||||
socket=Util.createSocket(host, port, TIMEOUT);
|
||||
socket.setTcpNoDelay(true);
|
||||
io=new IO();
|
||||
io.setInputStream(socket.getInputStream());
|
||||
io.setOutputStream(socket.getOutputStream());
|
||||
sendOpenConfirmation();
|
||||
}
|
||||
catch(Exception e){
|
||||
sendOpenFailure(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED);
|
||||
close=true;
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
thread=Thread.currentThread();
|
||||
Buffer buf=new Buffer(rmpsize);
|
||||
Packet packet=new Packet(buf);
|
||||
int i=0;
|
||||
try{
|
||||
while(thread!=null &&
|
||||
io!=null &&
|
||||
io.in!=null){
|
||||
i=io.in.read(buf.buffer,
|
||||
14,
|
||||
buf.buffer.length-14-Session.buffer_margin);
|
||||
if(i<=0){
|
||||
eof();
|
||||
break;
|
||||
}
|
||||
if(close)break;
|
||||
packet.reset();
|
||||
buf.putByte((byte)Session.SSH_MSG_CHANNEL_DATA);
|
||||
buf.putInt(recipient);
|
||||
buf.putInt(i);
|
||||
buf.skip(i);
|
||||
getSession().write(packet, this, i);
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
//System.err.println(e);
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
|
||||
private byte[] cache=new byte[0];
|
||||
private byte[] addCache(byte[] foo, int s, int l){
|
||||
byte[] bar=new byte[cache.length+l];
|
||||
System.arraycopy(foo, s, bar, cache.length, l);
|
||||
if(cache.length>0)
|
||||
System.arraycopy(cache, 0, bar, 0, cache.length);
|
||||
cache=bar;
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
void write(byte[] foo, int s, int l) throws IOException {
|
||||
//if(eof_local)return;
|
||||
|
||||
if(init){
|
||||
|
||||
Session _session=null;
|
||||
try{
|
||||
_session=getSession();
|
||||
}
|
||||
catch(JSchException e){
|
||||
throw new IOException(e.toString(), e);
|
||||
}
|
||||
|
||||
foo=addCache(foo, s, l);
|
||||
s=0;
|
||||
l=foo.length;
|
||||
|
||||
if(l<9)
|
||||
return;
|
||||
|
||||
int plen=(foo[s+6]&0xff)*256+(foo[s+7]&0xff);
|
||||
int dlen=(foo[s+8]&0xff)*256+(foo[s+9]&0xff);
|
||||
|
||||
if((foo[s]&0xff)==0x42){
|
||||
}
|
||||
else if((foo[s]&0xff)==0x6c){
|
||||
plen=((plen>>>8)&0xff)|((plen<<8)&0xff00);
|
||||
dlen=((dlen>>>8)&0xff)|((dlen<<8)&0xff00);
|
||||
}
|
||||
else{
|
||||
// ??
|
||||
}
|
||||
|
||||
if(l<12+plen+((-plen)&3)+dlen)
|
||||
return;
|
||||
|
||||
byte[] bar=new byte[dlen];
|
||||
System.arraycopy(foo, s+12+plen+((-plen)&3), bar, 0, dlen);
|
||||
byte[] faked_cookie=null;
|
||||
|
||||
synchronized(faked_cookie_pool){
|
||||
faked_cookie=faked_cookie_pool.get(_session);
|
||||
}
|
||||
|
||||
/*
|
||||
System.err.print("faked_cookie: ");
|
||||
for(int i=0; i<faked_cookie.length; i++){
|
||||
System.err.print(Integer.toHexString(faked_cookie[i]&0xff)+":");
|
||||
}
|
||||
System.err.println("");
|
||||
System.err.print("bar: ");
|
||||
for(int i=0; i<bar.length; i++){
|
||||
System.err.print(Integer.toHexString(bar[i]&0xff)+":");
|
||||
}
|
||||
System.err.println("");
|
||||
*/
|
||||
|
||||
if(equals(bar, faked_cookie)){
|
||||
if(cookie!=null)
|
||||
System.arraycopy(cookie, 0, foo, s+12+plen+((-plen)&3), dlen);
|
||||
}
|
||||
else{
|
||||
//System.err.println("wrong cookie");
|
||||
thread=null;
|
||||
eof();
|
||||
io.close();
|
||||
disconnect();
|
||||
}
|
||||
init=false;
|
||||
io.put(foo, s, l);
|
||||
cache=null;
|
||||
return;
|
||||
}
|
||||
io.put(foo, s, l);
|
||||
}
|
||||
|
||||
private static boolean equals(byte[] foo, byte[] bar){
|
||||
if(foo.length!=bar.length)return false;
|
||||
for(int i=0; i<foo.length; i++){
|
||||
if(foo[i]!=bar[i])return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
|
||||
/*
|
||||
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jcraft.jsch;
|
||||
|
||||
public interface Cipher{
|
||||
static int ENCRYPT_MODE=0;
|
||||
static int DECRYPT_MODE=1;
|
||||
int getIVSize();
|
||||
int getBlockSize();
|
||||
default int getTagSize() {return 0;}
|
||||
void init(int mode, byte[] key, byte[] iv) throws Exception;
|
||||
default void update(int foo) throws Exception {}
|
||||
void update(byte[] foo, int s1, int len, byte[] bar, int s2) throws Exception;
|
||||
default void updateAAD(byte[] foo, int s1, int len) throws Exception {}
|
||||
default void doFinal(byte[] foo, int s1, int len, byte[] bar, int s2) throws Exception {}
|
||||
boolean isCBC();
|
||||
default boolean isAEAD() {return false;}
|
||||
default boolean isChaCha20() {return false;}
|
||||
}
|