mirror of
https://github.com/moparisthebest/open-keychain
synced 2025-02-21 05:11:45 -05:00
Merge branch 'development' into linked-identities
Conflicts: Graphics/update-drawables.sh OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
This commit is contained in:
commit
b4aec3114d
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -1,15 +1,7 @@
|
||||
[submodule "extern/StickyListHeaders"]
|
||||
path = extern/StickyListHeaders
|
||||
url = https://github.com/open-keychain/StickyListHeaders.git
|
||||
ignore = dirty
|
||||
[submodule "extern/spongycastle"]
|
||||
path = extern/spongycastle
|
||||
url = https://github.com/open-keychain/spongycastle.git
|
||||
ignore = dirty
|
||||
[submodule "extern/html-textview"]
|
||||
path = extern/html-textview
|
||||
url = https://github.com/open-keychain/html-textview.git
|
||||
ignore = dirty
|
||||
[submodule "extern/openpgp-api-lib"]
|
||||
path = extern/openpgp-api-lib
|
||||
url = https://github.com/open-keychain/openpgp-api-lib.git
|
||||
@ -26,10 +18,6 @@
|
||||
path = extern/minidns
|
||||
url = https://github.com/open-keychain/minidns.git
|
||||
ignore = dirty
|
||||
[submodule "extern/TokenAutoComplete"]
|
||||
path = extern/TokenAutoComplete
|
||||
url = https://github.com/open-keychain/TokenAutoComplete
|
||||
ignore = dirty
|
||||
[submodule "extern/safeslinger-exchange"]
|
||||
path = extern/safeslinger-exchange
|
||||
url = https://github.com/open-keychain/exchange-android
|
||||
|
BIN
Graphics/drawables/glyph_authlite.png
Normal file
BIN
Graphics/drawables/glyph_authlite.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
BIN
Graphics/drawables/material-launcher/vector-src.pdf
Normal file
BIN
Graphics/drawables/material-launcher/vector-src.pdf
Normal file
Binary file not shown.
@ -1,213 +1,134 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="626px"
|
||||
height="626px" viewBox="0 0 626 626" enable-background="new 0 0 626 626" xml:space="preserve">
|
||||
<g id="Layer_9">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#94C061" d="M313.25,252H265.3c1.047-0.933,2.105-1.85,3.175-2.75h44.775V252z
|
||||
M313.25,313v18.75h-80.5V292.3c1.324-2.403,2.724-4.778,4.2-7.125V313H313.25z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#658D38" d="M232.75,292.3v39.45h80.5V313h3.7v-61h-3.7v-2.75h-44.775
|
||||
c1.307-1.109,2.632-2.192,3.975-3.25c2.202-1.742,4.435-3.408,6.7-5c4.153-2.919,8.412-5.585,12.775-8
|
||||
c9.974-5.517,20.499-9.717,31.575-12.6c12.134-3.167,24.934-4.75,38.4-4.75c9.199,0,18.083,0.75,26.649,2.25c3,0.5,6,1.117,9,1.85
|
||||
c24.967,6,47.217,18.583,66.75,37.75c0.334,0.333,0.667,0.667,1,1c21.334,21.367,34.717,45.983,40.15,73.85
|
||||
c0.2,1.167,0.416,2.351,0.649,3.551c1.4,8.3,2.101,16.85,2.101,25.649c0,0.134,0,0.283,0,0.45c0,22.934-4.601,43.967-13.8,63.1
|
||||
c-4.601,9.434-10.284,18.417-17.051,26.95c-2,2.4-4.033,4.783-6.1,7.15c-1.934,2.133-3.917,4.217-5.95,6.25
|
||||
c-3.5,3.5-7.066,6.8-10.7,9.899c-4.833,4.034-9.833,7.717-15,11.051c-5.333,3.399-10.833,6.416-16.5,9.05
|
||||
c-0.533,0.2-1.033,0.416-1.5,0.649c-17.466,7.867-36.483,11.967-57.05,12.301c-0.533,0-1.05,0-1.55,0c-0.366,0-0.733,0-1.1,0
|
||||
c-40.434,0-74.934-14.317-103.5-42.95c-0.467-0.467-0.917-0.917-1.35-1.351c-27.7-28.3-41.55-62.333-41.55-102.1
|
||||
c0-0.167,0-0.316,0-0.45c0.042-14.928,2.042-29.053,6-42.375c0.517-1.738,1.067-3.464,1.65-5.175
|
||||
C225.71,306.504,228.91,299.271,232.75,292.3z"/>
|
||||
</g>
|
||||
<g id="Layer_6">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#BBD89C" d="M304.3,301.2c-0.614-0.905-1.206-1.821-1.775-2.75
|
||||
c0.907-1.061,1.598-2.144,2.075-3.25c1.167-2.333,1.75-4.9,1.75-7.7c0-1.033-0.083-2.033-0.25-3
|
||||
c-0.368-2.332-1.21-4.482-2.525-6.45c0.112,0.118,0.204,0.209,0.275,0.275L304,278.5c2.9,3.256,4.35,7.09,4.35,11.5
|
||||
c0,1.759-0.233,3.425-0.7,5c-0.266,0.932-0.616,1.833-1.05,2.7C306.085,298.895,305.318,300.062,304.3,301.2z M296.825,306.425
|
||||
c-1.841,0.65-3.816,0.975-5.925,0.975c-2.68,0-5.146-0.533-7.4-1.6c-1.783-0.85-3.433-2.033-4.95-3.55
|
||||
c-0.894-0.895-1.669-1.836-2.325-2.825c0.106,0.106,0.214,0.214,0.325,0.325c3.434,3.433,7.55,5.15,12.35,5.15
|
||||
c2.171,0,4.205-0.35,6.1-1.05C295.588,304.717,296.197,305.575,296.825,306.425z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#7BAD45" d="M333.575,324.85c0.758-1.002,1.6-1.968,2.524-2.899
|
||||
c4.9-4.867,10.801-7.3,17.7-7.3c3.473,0,6.689,0.616,9.65,1.85c-0.393-0.553-0.81-1.095-1.25-1.625
|
||||
c0.193,0.112,0.385,0.229,0.575,0.35c0.349,0.262,0.69,0.537,1.024,0.825c1.32,1.517,2.354,3.133,3.101,4.851
|
||||
c0.1,0.199,0.199,0.383,0.3,0.55c1.1,2.033,1.866,4.184,2.3,6.45c0.063,0.356,0.121,0.715,0.175,1.074
|
||||
c-3.332,0.468-6.757,0.7-10.274,0.7C350.13,329.677,341.521,328.068,333.575,324.85z M436.125,303.05
|
||||
c-0.209-0.221-0.417-0.438-0.625-0.65c-1.628-1.637-3.294-3.195-5-4.675c2.639-5.218,4.613-10.71,5.925-16.475l25.851-25.775
|
||||
c0.342,0.343,0.684,0.685,1.024,1.025c21.334,21.367,34.717,45.983,40.15,73.85c0.2,1.167,0.416,2.351,0.649,3.551
|
||||
c1.4,8.3,2.101,16.85,2.101,25.649c0,0.134,0,0.283,0,0.45c0,22.934-4.601,43.967-13.8,63.1
|
||||
c-4.601,9.434-10.284,18.417-17.051,26.95c-0.075,0.091-0.15,0.183-0.225,0.275l5.975-21.976c0.101-0.399,0.051-0.816-0.149-1.25
|
||||
c-0.167-0.366-0.417-0.683-0.75-0.949c-0.134-0.034-8.167-8.784-24.101-26.25c3.448-7.858,5.157-14.851,5.125-20.976
|
||||
c-0.005-1.372-0.005-2.605,0-3.7c0.547-2.661,0.964-5.245,1.25-7.75c0.469-4.224,0.561-8.232,0.275-12.024
|
||||
c0.033-0.367,0.033-0.783,0-1.25c-0.4-2.634-0.884-5.25-1.45-7.851c-2.666-11.433-7.75-22.116-15.25-32.05
|
||||
c-1.333-1.7-2.7-3.383-4.1-5.05c-1.467-1.7-2.95-3.317-4.45-4.85C437.044,303.941,436.586,303.491,436.125,303.05z M255.05,462.1
|
||||
l46.725-46.574c1.695,2.331,3.52,4.623,5.475,6.875c0.874,0.999,1.757,1.975,2.65,2.925l51.075,81.075c-0.362,0-0.721,0-1.074,0
|
||||
c-40.434,0-74.934-14.317-103.5-42.95C255.947,462.998,255.498,462.548,255.05,462.1z M368.6,340.05
|
||||
c-1.316,3.908-3.649,7.324-7,10.25c-0.066,0.033-0.149,0.117-0.25,0.25c-0.1,0.033-0.149,0.084-0.149,0.15
|
||||
c-0.101,0.033-0.167,0.083-0.2,0.149c-0.434,0.367-0.866,0.733-1.3,1.101c-0.101,0.033-0.2,0.1-0.3,0.2
|
||||
c-2.434,1.8-4.9,3.017-7.4,3.649l-2.85,0.601c-0.267,0-0.517,0.033-0.75,0.1c-1,0.1-2.034,0.15-3.101,0.15c-0.1,0-0.2,0-0.3,0
|
||||
s-0.2,0-0.3,0c-0.101,0-0.2,0-0.3,0c-0.134-0.067-0.284-0.101-0.45-0.101h-0.25c-4.182-0.275-7.89-1.476-11.125-3.6
|
||||
c-2.517-3.904-3.775-8.338-3.775-13.3c0-2.092,0.226-4.092,0.675-6c9.717,4.448,20.358,6.682,31.926,6.699
|
||||
C363.842,340.347,366.242,340.246,368.6,340.05z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#94C061" d="M301.775,415.525L255.05,462.1
|
||||
c-27.7-28.304-41.55-62.337-41.55-102.1c0-0.167,0-0.316,0-0.45c0.089-36.006,11.589-67.339,34.5-94
|
||||
c1.905-2.214,3.888-4.397,5.95-6.55c0.805-0.837,1.622-1.671,2.45-2.5c4.5-4.5,9.184-8.667,14.05-12.5
|
||||
c0.165-0.13,0.332-0.264,0.5-0.4c0.336-0.264,0.669-0.522,1-0.775c0.154-0.119,0.304-0.235,0.45-0.35l13.15-8.95
|
||||
c0.752-0.458,1.511-0.908,2.275-1.35v0.025c0.41-0.238,0.818-0.479,1.225-0.725l1.225-0.675c0.469-0.25,0.935-0.5,1.4-0.75
|
||||
c4.524-2.416,9.158-4.565,13.9-6.45c4.045-1.601,8.171-3.009,12.375-4.225c1.177-0.34,2.36-0.665,3.55-0.975
|
||||
c12.141-3.167,24.94-4.75,38.4-4.75c9.199,0,18.083,0.75,26.649,2.25c3,0.5,6,1.117,9,1.85c7.557,1.815,14.865,4.231,21.925,7.25
|
||||
c6.836,10.689,10.252,22.79,10.25,36.3c0.002,10.708-2.14,20.524-6.425,29.45c-8.328-5.511-17.444-9.411-27.35-11.7
|
||||
c-0.233-0.066-0.45-0.117-0.65-0.15c-1.434-0.333-2.916-0.633-4.45-0.9c-0.199-0.033-0.35-0.05-0.449-0.05
|
||||
c-5.301-0.7-8.25-1.067-8.851-1.1h-0.05c-18.967-1.267-36.134,2.783-51.5,12.15c-4.233,2.567-8.316,5.55-12.25,8.95
|
||||
c-0.1,0.066-0.167,0.15-0.2,0.25c-0.133,0.1-0.283,0.233-0.45,0.4c-0.53,0.462-1.056,0.929-1.574,1.4
|
||||
c-2.051,1.869-3.984,3.803-5.8,5.8c-1.214-1.499-2.355-3.032-3.425-4.6c1.019-1.139,1.785-2.305,2.3-3.5
|
||||
c0.434-0.868,0.784-1.768,1.05-2.7c0.467-1.575,0.7-3.241,0.7-5c0-4.41-1.45-8.244-4.35-11.5l-0.15-0.175
|
||||
c-0.071-0.066-0.163-0.158-0.275-0.275c-0.069-0.076-0.144-0.159-0.225-0.25c-0.051-0.051-0.102-0.101-0.15-0.15
|
||||
c-0.633-0.633-1.317-1.217-2.05-1.75c-2.26-1.695-4.768-2.728-7.525-3.1H293.6c-0.432-0.1-0.898-0.149-1.4-0.15
|
||||
c-0.434-0.066-0.867-0.1-1.3-0.1c-0.333,0-0.65,0.034-0.95,0.1c-0.667,0-1.317,0.05-1.95,0.15h-0.05
|
||||
c-0.256,0.041-0.506,0.091-0.75,0.15l-2.2,0.6c-0.44,0.165-0.874,0.348-1.3,0.55c-0.117,0.05-0.233,0.1-0.35,0.15
|
||||
c-1.731,0.798-3.332,1.931-4.8,3.4c-0.2,0.2-0.383,0.417-0.55,0.65c-0.062,0.066-0.12,0.133-0.175,0.2
|
||||
c-1.904,2.117-3.179,4.5-3.825,7.15c-0.333,1.4-0.5,2.85-0.5,4.35c0,3.525,0.908,6.667,2.725,9.425
|
||||
c0.656,0.989,1.431,1.931,2.325,2.825c1.517,1.517,3.167,2.7,4.95,3.55c2.253,1.067,4.72,1.6,7.4,1.6
|
||||
c2.109,0,4.084-0.325,5.925-0.975c1.599,2.163,3.333,4.271,5.2,6.325c0.039-0.053,0.081-0.103,0.125-0.15
|
||||
c-0.357,0.478-0.708,0.961-1.05,1.45c-0.7,1-1.35,2-1.95,3c-1.6,2.434-3.083,4.967-4.45,7.601c-0.016,0.033-0.033,0.066-0.05,0.1
|
||||
c-1.014,2.101-1.964,4.217-2.85,6.35c-0.633,1.601-1.216,3.233-1.75,4.9c-2.267,6.967-3.683,14.384-4.25,22.25
|
||||
C284.278,379.587,289.603,398.679,301.775,415.525z M462.275,255.475l-25.851,25.775c1.307-5.716,1.966-11.699,1.976-17.95
|
||||
c-0.018-11.181-2.109-21.498-6.275-30.95C442.807,238.525,452.856,246.234,462.275,255.475z M362.2,314.875
|
||||
c-0.057-0.069-0.115-0.136-0.175-0.2c0.254,0.176,0.504,0.359,0.75,0.55C362.585,315.104,362.394,314.987,362.2,314.875z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#6C983D" d="M433,487.2c-3.879,2.285-7.846,4.368-11.9,6.25
|
||||
c-0.533,0.2-1.033,0.416-1.5,0.649c-17.453,7.864-36.47,11.956-57.05,12.275c-0.523,0.01-1.049,0.019-1.575,0.025L309.9,425.325
|
||||
c0.931,0.984,1.873,1.943,2.825,2.875c2.176,2.226,4.417,4.309,6.725,6.25c2.767,2.366,5.684,4.533,8.75,6.5
|
||||
c10.033,6.566,21.25,10.816,33.649,12.75c0.101,0,0.233,0,0.4,0c5.441-0.049,10.25-0.207,14.425-0.476
|
||||
c1.525-0.091,2.968-0.199,4.325-0.324c5.1-0.467,10.3-1.184,15.6-2.15l1.4,1.4v0.699c0.2-0.03,0.399-0.063,0.6-0.1L433,487.2z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#E2E2E2" d="M414.8,207.875c-14.804-14.35-32.604-21.525-53.399-21.525
|
||||
c-21.301,0-39.467,7.5-54.5,22.5c-3.536,3.543-6.652,7.26-9.35,11.15c-2.247,3.233-4.206,6.583-5.875,10.05
|
||||
c-0.465,0.25-0.931,0.5-1.4,0.75l-1.225,0.675c-0.406,0.246-0.815,0.487-1.225,0.725v-0.025
|
||||
c3.759-9.244,9.451-17.686,17.075-25.325c15.042-15.012,33.208-22.521,54.5-22.525c21.259,0.003,39.392,7.512,54.399,22.525
|
||||
C414.137,207.187,414.47,207.528,414.8,207.875z M408.7,213.95c0.331,0.331,0.664,0.665,1,1c3.316,3.316,6.225,6.799,8.725,10.45
|
||||
c7.55,11.081,11.325,23.714,11.325,37.9c0,10.513-2.066,20.171-6.2,28.975c-0.072,0.155-0.147,0.314-0.225,0.475
|
||||
c-0.525,1.09-1.084,2.165-1.675,3.225c-3.098,5.593-7.081,10.818-11.95,15.675c-4.908,4.92-10.191,8.937-15.851,12.05
|
||||
c-5.304,2.91-10.937,5.027-16.899,6.35c-1.729,0.384-3.486,0.7-5.275,0.95c-0.607,0.085-1.216,0.16-1.825,0.225
|
||||
c-2.757,0.317-5.573,0.476-8.449,0.476c-10.305,0-19.788-1.983-28.45-5.95c-2.097-0.97-4.146-2.053-6.15-3.25l-0.325,0.55
|
||||
c-0.111,0.182-0.22,0.365-0.324,0.55c0.322-0.596,0.673-1.18,1.05-1.75c2.078,1.124,4.203,2.124,6.375,3
|
||||
c7.946,3.219,16.555,4.827,25.825,4.825c3.518,0,6.942-0.232,10.274-0.7c0.526-0.073,1.052-0.156,1.575-0.25
|
||||
c1.565-0.254,3.106-0.562,4.625-0.925c11.948-2.813,22.557-8.863,31.825-18.15c5.637-5.626,10.078-11.743,13.325-18.35
|
||||
c0.096-0.186,0.188-0.369,0.274-0.55c4.285-8.926,6.427-18.742,6.425-29.45c0.002-13.51-3.414-25.61-10.25-36.3
|
||||
C414.997,221.133,412.072,217.45,408.7,213.95z M295,303.85c-6.065-8.954-9.948-18.82-11.65-29.6c0.117-0.05,0.233-0.1,0.35-0.15
|
||||
c0.426-0.203,0.859-0.386,1.3-0.55c1.337,10.945,4.862,20.97,10.575,30.075c0.549,0.882,1.115,1.757,1.7,2.625
|
||||
c0.852,1.233,1.744,2.45,2.675,3.65c0.707,0.909,1.44,1.809,2.2,2.7c-0.044,0.047-0.086,0.097-0.125,0.15
|
||||
c-1.867-2.054-3.601-4.163-5.2-6.325C296.197,305.575,295.588,304.717,295,303.85z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#D3D3D3" d="M305.575,223.6c-4.743,1.884-9.376,4.034-13.9,6.45
|
||||
c1.669-3.466,3.628-6.816,5.875-10.05c2.698-3.89,5.814-7.606,9.35-11.15c15.034-15,33.2-22.5,54.5-22.5
|
||||
c20.796,0,38.596,7.175,53.399,21.525c0.335,0.318,0.668,0.643,1,0.975c7.129,7.129,12.571,14.962,16.325,23.5
|
||||
c4.166,9.452,6.258,19.769,6.275,30.95c-0.01,6.251-0.669,12.234-1.976,17.95c-1.312,5.765-3.286,11.257-5.925,16.475
|
||||
c-0.037,0.074-0.07,0.148-0.1,0.225c-3.637,7.117-8.504,13.716-14.601,19.8c-10.662,10.686-22.903,17.577-36.725,20.675
|
||||
c-0.104,0.029-0.203,0.054-0.3,0.075c-2.47,0.547-4.986,0.972-7.551,1.275c-0.869,0.103-1.744,0.194-2.625,0.274
|
||||
c-2.357,0.196-4.758,0.297-7.199,0.3c-11.567-0.018-22.209-2.251-31.926-6.699c-1.929-0.879-3.821-1.846-5.675-2.9
|
||||
c0.047-0.277,0.098-0.552,0.15-0.825c0.015-0.092,0.031-0.184,0.05-0.274c0.173-0.857,0.39-1.69,0.65-2.5
|
||||
c0.388-1.194,0.871-2.336,1.449-3.426l0.051-0.125c0.104-0.185,0.213-0.368,0.324-0.55l0.325-0.55
|
||||
c2.004,1.197,4.054,2.28,6.15,3.25c8.662,3.967,18.146,5.95,28.45,5.95c2.876,0,5.692-0.158,8.449-0.476
|
||||
c0.609-0.064,1.218-0.14,1.825-0.225c1.789-0.25,3.547-0.566,5.275-0.95c5.963-1.322,11.596-3.439,16.899-6.35
|
||||
c5.659-3.113,10.942-7.13,15.851-12.05c4.869-4.857,8.853-10.082,11.95-15.675c0.591-1.061,1.149-2.135,1.675-3.225
|
||||
c0.077-0.161,0.152-0.32,0.225-0.475c4.134-8.804,6.2-18.462,6.2-28.975c0-14.186-3.775-26.819-11.325-37.9
|
||||
c-2.5-3.65-5.408-7.134-8.725-10.45c-0.336-0.335-0.669-0.669-1-1c-13.142-12.666-28.908-18.983-47.3-18.95
|
||||
c-18.9-0.033-35.034,6.617-48.4,19.95C310.24,217.717,307.766,220.6,305.575,223.6z M421.35,435.4l-3.1,2.449
|
||||
c-0.2,0.134-0.366,0.25-0.5,0.351c-0.267,0.2-0.517,0.366-0.75,0.5c-1.134,0.733-2.267,1.467-3.4,2.2
|
||||
c-0.1,0.033-0.183,0.083-0.25,0.149l65.601,65.75c0.133,0.134,0.316,0.316,0.55,0.55l1.1,1.101c1.4,1.366,3.067,2.05,5,2.05
|
||||
c1.634-0.066,3.051-0.55,4.25-1.45c0.301-0.166,0.551-0.383,0.75-0.649c0.233-0.167,1.25,0.399,3.051,1.699
|
||||
c1.767,1.334,5.116,4.817,10.05,10.45c4.866,5.533,11.833,9.533,20.899,12c-5.933,1-14.783,1.7-26.55,2.101
|
||||
c-11.121,0.349-19.721-2.201-25.8-7.65c-1.462-1.316-2.778-2.8-3.95-4.45l-20.1-20.1h0.05L433,487.2l-34.4-34.45l-0.6-0.6l-1.4-1.4
|
||||
c-5.3,0.967-10.5,1.684-15.6,2.15c-1.357,0.125-2.8,0.233-4.325,0.324l33.95-33.925c0.542,0.528,1.075,1.045,1.6,1.55
|
||||
c4.902,4.798,8.96,8.848,12.176,12.15c-0.167,0.066-0.284,0.167-0.351,0.3c-0.899,0.733-1.767,1.45-2.6,2.15L421.35,435.4z
|
||||
M302.15,312.6c-0.759-0.891-1.493-1.791-2.2-2.7c-0.932-1.2-1.823-2.417-2.675-3.65c-0.584-0.868-1.151-1.743-1.7-2.625
|
||||
c-5.713-9.105-9.238-19.13-10.575-30.075l2.2-0.6c0.244-0.06,0.494-0.109,0.75-0.15H288c0.633-0.1,1.283-0.15,1.95-0.15
|
||||
c0.3-0.066,0.617-0.1,0.95-0.1c0.434,0,0.867,0.034,1.3,0.1c0.501,0,0.968,0.05,1.4,0.15h0.025c0.946,7.487,3.054,14.487,6.325,21
|
||||
c0.793,1.581,1.651,3.131,2.575,4.65c0.569,0.929,1.161,1.845,1.775,2.75c1.07,1.567,2.211,3.101,3.425,4.6
|
||||
c-0.236,0.257-0.469,0.515-0.7,0.775L302.15,312.6z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#B6B6B6" d="M309.9,425.325c-0.893-0.95-1.776-1.926-2.65-2.925
|
||||
c-1.955-2.252-3.78-4.544-5.475-6.875c-12.172-16.847-17.497-35.938-15.975-57.275c0.567-7.866,1.983-15.283,4.25-22.25
|
||||
c0.534-1.667,1.117-3.3,1.75-4.9c0.886-2.133,1.836-4.249,2.85-6.35c0.017-0.033,0.034-0.066,0.05-0.1
|
||||
c1.367-2.634,2.85-5.167,4.45-7.601c0.6-1,1.25-2,1.95-3c0.342-0.488,0.692-0.972,1.05-1.45l4.875-6.025
|
||||
c0.231-0.26,0.464-0.519,0.7-0.775c1.816-1.997,3.75-3.931,5.8-5.8c0.519-0.471,1.044-0.938,1.574-1.4
|
||||
c0.167-0.167,0.317-0.3,0.45-0.4c0.033-0.1,0.101-0.184,0.2-0.25c3.934-3.4,8.017-6.383,12.25-8.95
|
||||
c15.366-9.367,32.533-13.417,51.5-12.15h0.05c0.601,0.033,3.55,0.4,8.851,1.1c0.1,0,0.25,0.017,0.449,0.05
|
||||
c1.534,0.267,3.017,0.567,4.45,0.9c0.2,0.033,0.417,0.083,0.65,0.15c9.905,2.289,19.021,6.189,27.35,11.7
|
||||
c-0.087,0.181-0.179,0.364-0.274,0.55c-7.707-4.751-16.065-8.167-25.075-10.25c-0.233-0.066-0.45-0.117-0.65-0.15
|
||||
c-1.434-0.333-2.916-0.633-4.45-0.9c-0.199-0.033-0.35-0.05-0.449-0.05c-5.301-0.7-8.25-1.067-8.851-1.1h-0.05
|
||||
c-18.98-1.281-36.146,2.769-51.5,12.15c-4.22,2.58-8.303,5.563-12.25,8.95c-0.1,0.066-0.167,0.15-0.2,0.25
|
||||
c-0.133,0.1-0.283,0.233-0.45,0.4c-5.433,4.733-10.1,9.883-14,15.45c-0.7,1-1.35,2-1.95,3c-1.6,2.434-3.083,4.967-4.45,7.601
|
||||
c-1.034,2.133-2,4.283-2.9,6.449c-0.634,1.608-1.217,3.242-1.75,4.9c-2.268,6.971-3.685,14.388-4.25,22.25
|
||||
c-1.451,20.351,3.324,38.659,14.325,54.925c2.144,3.148,4.519,6.224,7.125,9.226c0.08,0.091,0.163,0.183,0.25,0.274
|
||||
c1.057,1.209,2.132,2.384,3.225,3.525C311.773,427.269,310.831,426.31,309.9,425.325z M363.8,316.05
|
||||
c0.185,0.165,0.368,0.332,0.55,0.5c2.034,1.934,3.551,4.05,4.551,6.351c0.1,0.199,0.199,0.383,0.3,0.55
|
||||
c0.91,1.683,1.594,3.44,2.05,5.274c-0.523,0.094-1.049,0.177-1.575,0.25c-0.054-0.359-0.112-0.718-0.175-1.074
|
||||
c-0.434-2.267-1.2-4.417-2.3-6.45c-0.101-0.167-0.2-0.351-0.3-0.55C366.153,319.183,365.12,317.566,363.8,316.05z M329.9,350.9
|
||||
c0.853,0.762,1.744,1.445,2.675,2.05c3.235,2.124,6.943,3.324,11.125,3.6h0.25c0.166,0,0.316,0.033,0.45,0.101c0.1,0,0.199,0,0.3,0
|
||||
c0.1,0,0.2,0,0.3,0s0.2,0,0.3,0c1.066,0,2.101-0.051,3.101-0.15c0.233-0.066,0.483-0.1,0.75-0.1L352,355.8
|
||||
c2.5-0.633,4.967-1.85,7.4-3.649c0.1-0.101,0.199-0.167,0.3-0.2c0.434-0.367,0.866-0.733,1.3-1.101
|
||||
c0.033-0.066,0.1-0.116,0.2-0.149c0-0.066,0.05-0.117,0.149-0.15c0.101-0.133,0.184-0.217,0.25-0.25
|
||||
c3.351-2.926,5.684-6.342,7-10.25c0.881-0.08,1.756-0.172,2.625-0.274c-1.101,4.872-3.643,9.047-7.625,12.524
|
||||
c-0.066,0.033-0.149,0.117-0.25,0.25c-0.1,0.033-0.149,0.084-0.149,0.15c-0.101,0.033-0.167,0.083-0.2,0.149
|
||||
c-0.434,0.367-0.866,0.733-1.3,1.101c-0.101,0.033-0.2,0.1-0.3,0.2c-2.434,1.8-4.9,3.017-7.4,3.649l-2.85,0.601
|
||||
c-0.267,0-0.517,0.033-0.75,0.1c-1,0.1-2.034,0.15-3.101,0.15c-0.1,0-0.2,0-0.3,0s-0.2,0-0.3,0c-0.101,0-0.2,0-0.3,0
|
||||
c-0.134-0.067-0.284-0.101-0.45-0.101h-0.25c-5.009-0.33-9.343-1.98-13-4.95C331.72,352.795,330.787,351.895,329.9,350.9z
|
||||
M430.5,297.725c1.706,1.48,3.372,3.038,5,4.675c0.208,0.212,0.416,0.429,0.625,0.65c-1.864-1.801-3.772-3.501-5.725-5.1
|
||||
C430.43,297.874,430.463,297.799,430.5,297.725z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#A3A3A3" d="M462.475,367.475l-51.85,51.825l-33.95,33.925
|
||||
c-4.175,0.269-8.983,0.427-14.425,0.476c-0.167,0-0.3,0-0.4,0c-12.399-1.934-23.616-6.184-33.649-12.75
|
||||
c-3.066-1.967-5.983-4.134-8.75-6.5c-2.308-1.941-4.549-4.024-6.725-6.25c-1.093-1.142-2.168-2.316-3.225-3.525
|
||||
c-0.087-0.092-0.17-0.184-0.25-0.274c-2.606-3.002-4.981-6.077-7.125-9.226c-11.001-16.266-15.776-34.574-14.325-54.925
|
||||
c0.565-7.862,1.982-15.279,4.25-22.25c0.533-1.658,1.116-3.292,1.75-4.9c0.9-2.166,1.867-4.316,2.9-6.449
|
||||
c1.367-2.634,2.85-5.167,4.45-7.601c0.6-1,1.25-2,1.95-3c3.9-5.566,8.566-10.716,14-15.45c0.167-0.167,0.317-0.3,0.45-0.4
|
||||
c0.033-0.1,0.101-0.184,0.2-0.25c3.947-3.387,8.03-6.37,12.25-8.95c15.354-9.381,32.52-13.431,51.5-12.15h0.05
|
||||
c0.601,0.033,3.55,0.4,8.851,1.1c0.1,0,0.25,0.017,0.449,0.05c1.534,0.267,3.017,0.567,4.45,0.9c0.2,0.033,0.417,0.083,0.65,0.15
|
||||
c9.01,2.083,17.368,5.499,25.075,10.25c-3.247,6.607-7.688,12.724-13.325,18.35c-9.269,9.287-19.877,15.336-31.825,18.15
|
||||
c-1.519,0.363-3.06,0.671-4.625,0.925c-0.456-1.834-1.14-3.592-2.05-5.274c-0.101-0.167-0.2-0.351-0.3-0.55
|
||||
c-1-2.301-2.517-4.417-4.551-6.351c-0.182-0.168-0.365-0.335-0.55-0.5c-0.334-0.288-0.676-0.563-1.024-0.825
|
||||
c-0.246-0.19-0.496-0.374-0.75-0.55c-3.474-2.444-7.615-3.87-12.426-4.275c-2.301-0.139-4.501,0.011-6.6,0.45
|
||||
c-3.92,0.822-7.487,2.656-10.7,5.5c-0.233,0.167-0.45,0.351-0.649,0.551c-1.766,1.505-3.249,3.154-4.45,4.949
|
||||
c-0.377,0.57-0.728,1.154-1.05,1.75l-0.051,0.125c-0.578,1.09-1.062,2.231-1.449,3.426c-0.261,0.81-0.478,1.643-0.65,2.5
|
||||
c-0.019,0.091-0.035,0.183-0.05,0.274c-0.053,0.273-0.104,0.548-0.15,0.825c-0.114,0.75-0.198,1.517-0.25,2.3
|
||||
c0,0.033,0,0.084,0,0.15c-0.09,1.646-0.04,3.246,0.15,4.8c0.082,0.744,0.199,1.478,0.35,2.2c0.834,3.666,2.601,7.066,5.3,10.2
|
||||
c0.018,0.016,0.034,0.032,0.051,0.05h0.1c0.133,0.155,0.266,0.306,0.4,0.45c0.887,0.994,1.819,1.895,2.8,2.699
|
||||
c3.657,2.97,7.991,4.62,13,4.95h0.25c0.166,0,0.316,0.033,0.45,0.101c0.1,0,0.199,0,0.3,0c0.1,0,0.2,0,0.3,0s0.2,0,0.3,0
|
||||
c1.066,0,2.101-0.051,3.101-0.15c0.233-0.066,0.483-0.1,0.75-0.1L354,357.8c2.5-0.633,4.967-1.85,7.4-3.649
|
||||
c0.1-0.101,0.199-0.167,0.3-0.2c0.434-0.367,0.866-0.733,1.3-1.101c0.033-0.066,0.1-0.116,0.2-0.149c0-0.066,0.05-0.117,0.149-0.15
|
||||
c0.101-0.133,0.184-0.217,0.25-0.25c3.982-3.478,6.524-7.652,7.625-12.524c2.564-0.304,5.081-0.729,7.551-1.275
|
||||
c0.097-0.021,0.196-0.046,0.3-0.075c13.821-3.098,26.063-9.989,36.725-20.675c6.097-6.083,10.964-12.683,14.601-19.8
|
||||
c1.952,1.598,3.86,3.298,5.725,5.1c0.461,0.441,0.919,0.892,1.375,1.35c1.5,1.533,2.983,3.15,4.45,4.85
|
||||
c1.399,1.667,2.767,3.35,4.1,5.05c7.5,9.934,12.584,20.617,15.25,32.05c0.566,2.601,1.05,5.217,1.45,7.851
|
||||
c0.033,0.467,0.033,0.883,0,1.25C463.035,359.242,462.943,363.251,462.475,367.475z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#8F8F8F" d="M474.175,453.9h-0.024v0.05c-0.367,0.066-0.75,0.033-1.15-0.101
|
||||
c-0.192-0.096-0.359-0.229-0.5-0.399c-0.115-0.131-0.216-0.281-0.3-0.45v-0.15c-0.134-0.333-0.134-0.683,0-1.05l-0.05,0.101
|
||||
l6.949-25.551c0.101-0.399,0.051-0.816-0.149-1.25c-0.167-0.366-0.417-0.683-0.75-0.949c-0.134-0.034-8.167-8.784-24.101-26.25
|
||||
c1.967-3.834,4.051-10.117,6.25-18.851c0.32-1.295,0.612-2.57,0.875-3.825c-0.005,1.095-0.005,2.328,0,3.7
|
||||
c0.032,6.125-1.677,13.117-5.125,20.976c15.934,17.466,23.967,26.216,24.101,26.25c0.333,0.267,0.583,0.583,0.75,0.949
|
||||
c0.2,0.434,0.25,0.851,0.149,1.25l-5.975,21.976l-0.975,3.575l0.05-0.101C474.188,453.832,474.18,453.865,474.175,453.9z
|
||||
M527.95,473.35c6.564,5.904,9.681,14.588,9.35,26.051c-0.467,13.733-1.383,23.517-2.75,29.35c-0.233,1.167-0.767,2.25-1.6,3.25
|
||||
l-0.15,0.3c-0.333,0.233-0.649,0.45-0.95,0.65c-0.8,0.533-1.666,0.866-2.6,1c-0.8,0.2-1.684,0.399-2.65,0.6
|
||||
c-5.933,1-14.783,1.7-26.55,2.101c-12.402,0.389-21.669-2.827-27.8-9.65c6.079,5.449,14.679,7.999,25.8,7.65
|
||||
c11.767-0.4,20.617-1.101,26.55-2.101c0.967-0.2,1.851-0.399,2.65-0.6c0.934-0.134,1.8-0.467,2.6-1
|
||||
c0.301-0.2,0.617-0.417,0.95-0.65l0.15-0.3c0.833-1,1.366-2.083,1.6-3.25c1.367-5.833,2.283-15.616,2.75-29.35
|
||||
C535.596,487.171,533.146,479.154,527.95,473.35z M496.825,476.575c-0.357,0.039-0.698,0.014-1.025-0.075
|
||||
c-0.333-0.2-0.6-0.467-0.8-0.8c-0.066-0.066-0.1-0.15-0.1-0.25c-0.134-0.334-0.15-0.667-0.051-1l0.051-0.05l6.75-25.051
|
||||
c0.1-0.433,0.083-0.85-0.051-1.25c1.423,1.927,2.105,3.01,2.051,3.25L496.9,476.4l-0.051,0.05
|
||||
C496.838,476.489,496.83,476.531,496.825,476.575z M398.6,452.75c-0.2,0.036-0.399,0.069-0.6,0.1v-0.699L398.6,452.75z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#DBDBDB" d="M410.625,419.3l51.85-51.825c-0.286,2.505-0.703,5.089-1.25,7.75
|
||||
c-0.263,1.255-0.555,2.53-0.875,3.825c-2.199,8.733-4.283,15.017-6.25,18.851c15.934,17.466,23.967,26.216,24.101,26.25
|
||||
c0.333,0.267,0.583,0.583,0.75,0.949c0.2,0.434,0.25,0.851,0.149,1.25L472.15,451.9l0.05-0.101c-0.134,0.367-0.134,0.717,0,1.05
|
||||
V453c0.084,0.169,0.185,0.319,0.3,0.45c0.141,0.17,0.308,0.304,0.5,0.399c0.4,0.134,0.783,0.167,1.15,0.101v-0.05h0.024
|
||||
l25.325-6.65c0.434-0.233,0.833-0.217,1.2,0.05c0.333,0.167,0.633,0.434,0.899,0.8c0.134,0.4,0.15,0.817,0.051,1.25L494.9,474.4
|
||||
l-0.051,0.05c-0.1,0.333-0.083,0.666,0.051,1c0,0.1,0.033,0.184,0.1,0.25c0.2,0.333,0.467,0.6,0.8,0.8
|
||||
c0.327,0.089,0.668,0.114,1.025,0.075c0.039-0.01,0.081-0.018,0.125-0.025v0.101l0.1-0.101l26.65-7
|
||||
c1.576,1.142,2.992,2.408,4.25,3.8c5.195,5.805,7.646,13.821,7.35,24.051c-0.467,13.733-1.383,23.517-2.75,29.35
|
||||
c-0.233,1.167-0.767,2.25-1.6,3.25l-0.15,0.3c-0.333,0.233-0.649,0.45-0.95,0.65c-0.8,0.533-1.666,0.866-2.6,1
|
||||
c-0.8,0.2-1.684,0.399-2.65,0.6c-21.5-21.6-32.816-32.934-33.949-34L430.55,438.2c-0.2-0.101-0.366-0.233-0.5-0.4l-5.2-5.25
|
||||
l-0.149,0.15c-0.101,0.066-0.2,0.166-0.3,0.3c-3.216-3.303-7.273-7.353-12.176-12.15C411.7,420.345,411.167,419.828,410.625,419.3z
|
||||
M421.35,435.4l0.101,0.05h-0.101V435.4z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#C8C8C8" d="M421.35,435.4v0.05h0.101c0.833-0.7,1.7-1.417,2.6-2.15
|
||||
c0.066-0.133,0.184-0.233,0.351-0.3c0.1-0.134,0.199-0.233,0.3-0.3l0.149-0.15l5.2,5.25c0.134,0.167,0.3,0.3,0.5,0.4l60.101,60.35
|
||||
c1.133,1.066,12.449,12.4,33.949,34c-9.066-2.467-16.033-6.467-20.899-12c-4.934-5.633-8.283-9.116-10.05-10.45
|
||||
c-1.801-1.3-2.817-1.866-3.051-1.699c-0.199,0.267-0.449,0.483-0.75,0.649c-1.199,0.9-2.616,1.384-4.25,1.45
|
||||
c-1.933,0-3.6-0.684-5-2.05l-1.1-1.101c-0.233-0.233-0.417-0.416-0.55-0.55l-65.601-65.75c0.067-0.066,0.15-0.116,0.25-0.149
|
||||
c1.134-0.733,2.267-1.467,3.4-2.2c0.233-0.134,0.483-0.3,0.75-0.5c0.134-0.101,0.3-0.217,0.5-0.351L421.35,435.4z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="626px"
|
||||
height="626px"
|
||||
viewBox="0 0 626 626"
|
||||
enable-background="new 0 0 626 626"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="vector-src.svg"><metadata
|
||||
id="metadata36"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs34" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2558"
|
||||
inkscape:window-height="1419"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.266577"
|
||||
inkscape:cx="-305.84541"
|
||||
inkscape:cy="354.73238"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="19"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
|
||||
<g
|
||||
id="g3013"
|
||||
transform="translate(-20.480691,-1.8781185)"><g
|
||||
transform="matrix(1.6991779,0,0,1.6991779,-304.41094,-297.68272)"
|
||||
id="Layer_9">
|
||||
<path
|
||||
style="fill:#94c061;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5"
|
||||
d="M 313.25,252 H 265.3 c 1.047,-0.933 2.105,-1.85 3.175,-2.75 H 313.25 V 252 z m 0,61 v 18.75 h -80.5 V 292.3 c 1.324,-2.403 2.724,-4.778 4.2,-7.125 V 313 h 76.3 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#658d38;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7"
|
||||
d="m 232.75,292.3 v 39.45 h 80.5 V 313 h 3.7 v -61 h -3.7 v -2.75 h -44.775 c 1.307,-1.109 2.632,-2.192 3.975,-3.25 2.202,-1.742 4.435,-3.408 6.7,-5 4.153,-2.919 8.412,-5.585 12.775,-8 9.974,-5.517 20.499,-9.717 31.575,-12.6 12.134,-3.167 24.934,-4.75 38.4,-4.75 9.199,0 18.083,0.75 26.649,2.25 3,0.5 6,1.117 9,1.85 24.967,6 47.217,18.583 66.75,37.75 0.334,0.333 0.667,0.667 1,1 21.334,21.367 34.717,45.983 40.15,73.85 0.2,1.167 0.416,2.351 0.649,3.551 1.4,8.3 2.101,16.85 2.101,25.649 0,0.134 0,0.283 0,0.45 0,22.934 -4.601,43.967 -13.8,63.1 -4.601,9.434 -10.284,18.417 -17.051,26.95 -2,2.4 -4.033,4.783 -6.1,7.15 -1.934,2.133 -3.917,4.217 -5.95,6.25 -3.5,3.5 -7.066,6.8 -10.7,9.899 -4.833,4.034 -9.833,7.717 -15,11.051 -5.333,3.399 -10.833,6.416 -16.5,9.05 -0.533,0.2 -1.033,0.416 -1.5,0.649 -17.466,7.867 -36.483,11.967 -57.05,12.301 -0.533,0 -1.05,0 -1.55,0 -0.366,0 -0.733,0 -1.1,0 -40.434,0 -74.934,-14.317 -103.5,-42.95 -0.467,-0.467 -0.917,-0.917 -1.35,-1.351 -27.7,-28.3 -41.55,-62.333 -41.55,-102.1 0,-0.167 0,-0.316 0,-0.45 0.042,-14.928 2.042,-29.053 6,-42.375 0.517,-1.738 1.067,-3.464 1.65,-5.175 2.562,-7.495 5.762,-14.728 9.602,-21.699 z"
|
||||
clip-rule="evenodd" />
|
||||
</g><g
|
||||
transform="matrix(1.6991779,0,0,1.6991779,-304.41094,-297.68272)"
|
||||
id="Layer_6">
|
||||
<path
|
||||
style="fill:#bbd89c;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10"
|
||||
d="m 304.3,301.2 c -0.614,-0.905 -1.206,-1.821 -1.775,-2.75 0.907,-1.061 1.598,-2.144 2.075,-3.25 1.167,-2.333 1.75,-4.9 1.75,-7.7 0,-1.033 -0.083,-2.033 -0.25,-3 -0.368,-2.332 -1.21,-4.482 -2.525,-6.45 0.112,0.118 0.204,0.209 0.275,0.275 L 304,278.5 c 2.9,3.256 4.35,7.09 4.35,11.5 0,1.759 -0.233,3.425 -0.7,5 -0.266,0.932 -0.616,1.833 -1.05,2.7 -0.515,1.195 -1.282,2.362 -2.3,3.5 z m -7.475,5.225 c -1.841,0.65 -3.816,0.975 -5.925,0.975 -2.68,0 -5.146,-0.533 -7.4,-1.6 -1.783,-0.85 -3.433,-2.033 -4.95,-3.55 -0.894,-0.895 -1.669,-1.836 -2.325,-2.825 0.106,0.106 0.214,0.214 0.325,0.325 3.434,3.433 7.55,5.15 12.35,5.15 2.171,0 4.205,-0.35 6.1,-1.05 0.588,0.867 1.197,1.725 1.825,2.575 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#7bad45;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12"
|
||||
d="m 333.575,324.85 c 0.758,-1.002 1.6,-1.968 2.524,-2.899 4.9,-4.867 10.801,-7.3 17.7,-7.3 3.473,0 6.689,0.616 9.65,1.85 -0.393,-0.553 -0.81,-1.095 -1.25,-1.625 0.193,0.112 0.385,0.229 0.575,0.35 0.349,0.262 0.69,0.537 1.024,0.825 1.32,1.517 2.354,3.133 3.101,4.851 0.1,0.199 0.199,0.383 0.3,0.55 1.1,2.033 1.866,4.184 2.3,6.45 0.063,0.356 0.121,0.715 0.175,1.074 -3.332,0.468 -6.757,0.7 -10.274,0.7 -9.27,0.001 -17.879,-1.608 -25.825,-4.826 z m 102.55,-21.8 c -0.209,-0.221 -0.417,-0.438 -0.625,-0.65 -1.628,-1.637 -3.294,-3.195 -5,-4.675 2.639,-5.218 4.613,-10.71 5.925,-16.475 l 25.851,-25.775 c 0.342,0.343 0.684,0.685 1.024,1.025 21.334,21.367 34.717,45.983 40.15,73.85 0.2,1.167 0.416,2.351 0.649,3.551 1.4,8.3 2.101,16.85 2.101,25.649 0,0.134 0,0.283 0,0.45 0,22.934 -4.601,43.967 -13.8,63.1 -4.601,9.434 -10.284,18.417 -17.051,26.95 -0.075,0.091 -0.15,0.183 -0.225,0.275 l 5.975,-21.976 c 0.101,-0.399 0.051,-0.816 -0.149,-1.25 -0.167,-0.366 -0.417,-0.683 -0.75,-0.949 -0.134,-0.034 -8.167,-8.784 -24.101,-26.25 3.448,-7.858 5.157,-14.851 5.125,-20.976 -0.005,-1.372 -0.005,-2.605 0,-3.7 0.547,-2.661 0.964,-5.245 1.25,-7.75 0.469,-4.224 0.561,-8.232 0.275,-12.024 0.033,-0.367 0.033,-0.783 0,-1.25 -0.4,-2.634 -0.884,-5.25 -1.45,-7.851 -2.666,-11.433 -7.75,-22.116 -15.25,-32.05 -1.333,-1.7 -2.7,-3.383 -4.1,-5.05 -1.467,-1.7 -2.95,-3.317 -4.45,-4.85 -0.455,-0.458 -0.913,-0.908 -1.374,-1.349 z M 255.05,462.1 301.775,415.526 c 1.695,2.331 3.52,4.623 5.475,6.875 0.874,0.999 1.757,1.975 2.65,2.925 l 51.075,81.075 c -0.362,0 -0.721,0 -1.074,0 -40.434,0 -74.934,-14.317 -103.5,-42.95 -0.454,-0.453 -0.903,-0.903 -1.351,-1.351 z M 368.6,340.05 c -1.316,3.908 -3.649,7.324 -7,10.25 -0.066,0.033 -0.149,0.117 -0.25,0.25 -0.1,0.033 -0.149,0.084 -0.149,0.15 -0.101,0.033 -0.167,0.083 -0.2,0.149 -0.434,0.367 -0.866,0.733 -1.3,1.101 -0.101,0.033 -0.2,0.1 -0.3,0.2 -2.434,1.8 -4.9,3.017 -7.4,3.649 l -2.85,0.601 c -0.267,0 -0.517,0.033 -0.75,0.1 -1,0.1 -2.034,0.15 -3.101,0.15 -0.1,0 -0.2,0 -0.3,0 -0.1,0 -0.2,0 -0.3,0 -0.101,0 -0.2,0 -0.3,0 -0.134,-0.067 -0.284,-0.101 -0.45,-0.101 h -0.25 c -4.182,-0.275 -7.89,-1.476 -11.125,-3.6 -2.517,-3.904 -3.775,-8.338 -3.775,-13.3 0,-2.092 0.226,-4.092 0.675,-6 9.717,4.448 20.358,6.682 31.926,6.699 2.441,-0.001 4.841,-0.102 7.199,-0.298 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#94c061;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path14"
|
||||
d="M 301.775,415.525 255.05,462.1 C 227.35,433.796 213.5,399.763 213.5,360 c 0,-0.167 0,-0.316 0,-0.45 0.089,-36.006 11.589,-67.339 34.5,-94 1.905,-2.214 3.888,-4.397 5.95,-6.55 0.805,-0.837 1.622,-1.671 2.45,-2.5 4.5,-4.5 9.184,-8.667 14.05,-12.5 0.165,-0.13 0.332,-0.264 0.5,-0.4 0.336,-0.264 0.669,-0.522 1,-0.775 0.154,-0.119 0.304,-0.235 0.45,-0.35 l 13.15,-8.95 c 0.752,-0.458 1.511,-0.908 2.275,-1.35 v 0.025 c 0.41,-0.238 0.818,-0.479 1.225,-0.725 l 1.225,-0.675 c 0.469,-0.25 0.935,-0.5 1.4,-0.75 4.524,-2.416 9.158,-4.565 13.9,-6.45 4.045,-1.601 8.171,-3.009 12.375,-4.225 1.177,-0.34 2.36,-0.665 3.55,-0.975 12.141,-3.167 24.94,-4.75 38.4,-4.75 9.199,0 18.083,0.75 26.649,2.25 3,0.5 6,1.117 9,1.85 7.557,1.815 14.865,4.231 21.925,7.25 6.836,10.689 10.252,22.79 10.25,36.3 0.002,10.708 -2.14,20.524 -6.425,29.45 -8.328,-5.511 -17.444,-9.411 -27.35,-11.7 -0.233,-0.066 -0.45,-0.117 -0.65,-0.15 -1.434,-0.333 -2.916,-0.633 -4.45,-0.9 -0.199,-0.033 -0.35,-0.05 -0.449,-0.05 -5.301,-0.7 -8.25,-1.067 -8.851,-1.1 h -0.05 c -18.967,-1.267 -36.134,2.783 -51.5,12.15 -4.233,2.567 -8.316,5.55 -12.25,8.95 -0.1,0.066 -0.167,0.15 -0.2,0.25 -0.133,0.1 -0.283,0.233 -0.45,0.4 -0.53,0.462 -1.056,0.929 -1.574,1.4 -2.051,1.869 -3.984,3.803 -5.8,5.8 -1.214,-1.499 -2.355,-3.032 -3.425,-4.6 1.019,-1.139 1.785,-2.305 2.3,-3.5 0.434,-0.868 0.784,-1.768 1.05,-2.7 0.467,-1.575 0.7,-3.241 0.7,-5 0,-4.41 -1.45,-8.244 -4.35,-11.5 l -0.15,-0.175 c -0.071,-0.066 -0.163,-0.158 -0.275,-0.275 -0.069,-0.076 -0.144,-0.159 -0.225,-0.25 -0.051,-0.051 -0.102,-0.101 -0.15,-0.15 -0.633,-0.633 -1.317,-1.217 -2.05,-1.75 -2.26,-1.695 -4.768,-2.728 -7.525,-3.1 H 293.6 c -0.432,-0.1 -0.898,-0.149 -1.4,-0.15 -0.434,-0.066 -0.867,-0.1 -1.3,-0.1 -0.333,0 -0.65,0.034 -0.95,0.1 -0.667,0 -1.317,0.05 -1.95,0.15 h -0.05 c -0.256,0.041 -0.506,0.091 -0.75,0.15 l -2.2,0.6 c -0.44,0.165 -0.874,0.348 -1.3,0.55 -0.117,0.05 -0.233,0.1 -0.35,0.15 -1.731,0.798 -3.332,1.931 -4.8,3.4 -0.2,0.2 -0.383,0.417 -0.55,0.65 -0.062,0.066 -0.12,0.133 -0.175,0.2 -1.904,2.117 -3.179,4.5 -3.825,7.15 -0.333,1.4 -0.5,2.85 -0.5,4.35 0,3.525 0.908,6.667 2.725,9.425 0.656,0.989 1.431,1.931 2.325,2.825 1.517,1.517 3.167,2.7 4.95,3.55 2.253,1.067 4.72,1.6 7.4,1.6 2.109,0 4.084,-0.325 5.925,-0.975 1.599,2.163 3.333,4.271 5.2,6.325 0.039,-0.053 0.081,-0.103 0.125,-0.15 -0.357,0.478 -0.708,0.961 -1.05,1.45 -0.7,1 -1.35,2 -1.95,3 -1.6,2.434 -3.083,4.967 -4.45,7.601 -0.016,0.033 -0.033,0.066 -0.05,0.1 -1.014,2.101 -1.964,4.217 -2.85,6.35 -0.633,1.601 -1.216,3.233 -1.75,4.9 -2.267,6.967 -3.683,14.384 -4.25,22.25 -1.522,21.336 3.803,40.428 15.975,57.274 z m 160.5,-160.05 -25.851,25.775 c 1.307,-5.716 1.966,-11.699 1.976,-17.95 -0.018,-11.181 -2.109,-21.498 -6.275,-30.95 10.682,6.175 20.731,13.884 30.15,23.125 z m -100.075,59.4 c -0.057,-0.069 -0.115,-0.136 -0.175,-0.2 0.254,0.176 0.504,0.359 0.75,0.55 -0.19,-0.121 -0.381,-0.238 -0.575,-0.35 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#6c983d;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16"
|
||||
d="m 433,487.2 c -3.879,2.285 -7.846,4.368 -11.9,6.25 -0.533,0.2 -1.033,0.416 -1.5,0.649 -17.453,7.864 -36.47,11.956 -57.05,12.275 -0.523,0.01 -1.049,0.019 -1.575,0.025 L 309.9,425.325 c 0.931,0.984 1.873,1.943 2.825,2.875 2.176,2.226 4.417,4.309 6.725,6.25 2.767,2.366 5.684,4.533 8.75,6.5 10.033,6.566 21.25,10.816 33.649,12.75 0.101,0 0.233,0 0.4,0 5.441,-0.049 10.25,-0.207 14.425,-0.476 1.525,-0.091 2.968,-0.199 4.325,-0.324 5.1,-0.467 10.3,-1.184 15.6,-2.15 l 1.4,1.4 v 0.699 c 0.2,-0.03 0.399,-0.063 0.6,-0.1 L 433,487.2 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#e2e2e2;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path18"
|
||||
d="m 414.8,207.875 c -14.804,-14.35 -32.604,-21.525 -53.399,-21.525 -21.301,0 -39.467,7.5 -54.5,22.5 -3.536,3.543 -6.652,7.26 -9.35,11.15 -2.247,3.233 -4.206,6.583 -5.875,10.05 -0.465,0.25 -0.931,0.5 -1.4,0.75 l -1.225,0.675 c -0.406,0.246 -0.815,0.487 -1.225,0.725 v -0.025 c 3.759,-9.244 9.451,-17.686 17.075,-25.325 15.042,-15.012 33.208,-22.521 54.5,-22.525 21.259,0.003 39.392,7.512 54.399,22.525 0.337,0.337 0.67,0.678 1,1.025 z m -6.1,6.075 c 0.331,0.331 0.664,0.665 1,1 3.316,3.316 6.225,6.799 8.725,10.45 7.55,11.081 11.325,23.714 11.325,37.9 0,10.513 -2.066,20.171 -6.2,28.975 -0.072,0.155 -0.147,0.314 -0.225,0.475 -0.525,1.09 -1.084,2.165 -1.675,3.225 -3.098,5.593 -7.081,10.818 -11.95,15.675 -4.908,4.92 -10.191,8.937 -15.851,12.05 -5.304,2.91 -10.937,5.027 -16.899,6.35 -1.729,0.384 -3.486,0.7 -5.275,0.95 -0.607,0.085 -1.216,0.16 -1.825,0.225 -2.757,0.317 -5.573,0.476 -8.449,0.476 -10.305,0 -19.788,-1.983 -28.45,-5.95 -2.097,-0.97 -4.146,-2.053 -6.15,-3.25 l -0.325,0.55 c -0.111,0.182 -0.22,0.365 -0.324,0.55 0.322,-0.596 0.673,-1.18 1.05,-1.75 2.078,1.124 4.203,2.124 6.375,3 7.946,3.219 16.555,4.827 25.825,4.825 3.518,0 6.942,-0.232 10.274,-0.7 0.526,-0.073 1.052,-0.156 1.575,-0.25 1.565,-0.254 3.106,-0.562 4.625,-0.925 11.948,-2.813 22.557,-8.863 31.825,-18.15 5.637,-5.626 10.078,-11.743 13.325,-18.35 0.096,-0.186 0.188,-0.369 0.274,-0.55 4.285,-8.926 6.427,-18.742 6.425,-29.45 0.002,-13.51 -3.414,-25.61 -10.25,-36.3 -2.478,-3.868 -5.403,-7.551 -8.775,-11.051 z M 295,303.85 c -6.065,-8.954 -9.948,-18.82 -11.65,-29.6 0.117,-0.05 0.233,-0.1 0.35,-0.15 0.426,-0.203 0.859,-0.386 1.3,-0.55 1.337,10.945 4.862,20.97 10.575,30.075 0.549,0.882 1.115,1.757 1.7,2.625 0.852,1.233 1.744,2.45 2.675,3.65 0.707,0.909 1.44,1.809 2.2,2.7 -0.044,0.047 -0.086,0.097 -0.125,0.15 -1.867,-2.054 -3.601,-4.163 -5.2,-6.325 -0.628,-0.85 -1.237,-1.708 -1.825,-2.575 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#d3d3d3;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path20"
|
||||
d="m 305.575,223.6 c -4.743,1.884 -9.376,4.034 -13.9,6.45 1.669,-3.466 3.628,-6.816 5.875,-10.05 2.698,-3.89 5.814,-7.606 9.35,-11.15 15.034,-15 33.2,-22.5 54.5,-22.5 20.796,0 38.596,7.175 53.399,21.525 0.335,0.318 0.668,0.643 1,0.975 7.129,7.129 12.571,14.962 16.325,23.5 4.166,9.452 6.258,19.769 6.275,30.95 -0.01,6.251 -0.669,12.234 -1.976,17.95 -1.312,5.765 -3.286,11.257 -5.925,16.475 -0.037,0.074 -0.07,0.148 -0.1,0.225 -3.637,7.117 -8.504,13.716 -14.601,19.8 -10.662,10.686 -22.903,17.577 -36.725,20.675 -0.104,0.029 -0.203,0.054 -0.3,0.075 -2.47,0.547 -4.986,0.972 -7.551,1.275 -0.869,0.103 -1.744,0.194 -2.625,0.274 -2.357,0.196 -4.758,0.297 -7.199,0.3 -11.567,-0.018 -22.209,-2.251 -31.926,-6.699 -1.929,-0.879 -3.821,-1.846 -5.675,-2.9 0.047,-0.277 0.098,-0.552 0.15,-0.825 0.015,-0.092 0.031,-0.184 0.05,-0.274 0.173,-0.857 0.39,-1.69 0.65,-2.5 0.388,-1.194 0.871,-2.336 1.449,-3.426 l 0.051,-0.125 c 0.104,-0.185 0.213,-0.368 0.324,-0.55 l 0.325,-0.55 c 2.004,1.197 4.054,2.28 6.15,3.25 8.662,3.967 18.146,5.95 28.45,5.95 2.876,0 5.692,-0.158 8.449,-0.476 0.609,-0.064 1.218,-0.14 1.825,-0.225 1.789,-0.25 3.547,-0.566 5.275,-0.95 5.963,-1.322 11.596,-3.439 16.899,-6.35 5.659,-3.113 10.942,-7.13 15.851,-12.05 4.869,-4.857 8.853,-10.082 11.95,-15.675 0.591,-1.061 1.149,-2.135 1.675,-3.225 0.077,-0.161 0.152,-0.32 0.225,-0.475 4.134,-8.804 6.2,-18.462 6.2,-28.975 0,-14.186 -3.775,-26.819 -11.325,-37.9 -2.5,-3.65 -5.408,-7.134 -8.725,-10.45 -0.336,-0.335 -0.669,-0.669 -1,-1 -13.142,-12.666 -28.908,-18.983 -47.3,-18.95 -18.9,-0.033 -35.034,6.617 -48.4,19.95 -2.754,2.768 -5.228,5.651 -7.419,8.651 z m 115.775,211.8 -3.1,2.449 c -0.2,0.134 -0.366,0.25 -0.5,0.351 -0.267,0.2 -0.517,0.366 -0.75,0.5 -1.134,0.733 -2.267,1.467 -3.4,2.2 -0.1,0.033 -0.183,0.083 -0.25,0.149 l 65.601,65.75 c 0.133,0.134 0.316,0.316 0.55,0.55 l 1.1,1.101 c 1.4,1.366 3.067,2.05 5,2.05 1.634,-0.066 3.051,-0.55 4.25,-1.45 0.301,-0.166 0.551,-0.383 0.75,-0.649 0.233,-0.167 1.25,0.399 3.051,1.699 1.767,1.334 5.116,4.817 10.05,10.45 4.866,5.533 11.833,9.533 20.899,12 -5.933,1 -14.783,1.7 -26.55,2.101 -11.121,0.349 -19.721,-2.201 -25.8,-7.65 -1.462,-1.316 -2.778,-2.8 -3.95,-4.45 l -20.1,-20.1 h 0.05 L 433,487.2 l -34.4,-34.45 -0.6,-0.6 -1.4,-1.4 c -5.3,0.967 -10.5,1.684 -15.6,2.15 -1.357,0.125 -2.8,0.233 -4.325,0.324 l 33.95,-33.925 c 0.542,0.528 1.075,1.045 1.6,1.55 4.902,4.798 8.96,8.848 12.176,12.15 -0.167,0.066 -0.284,0.167 -0.351,0.3 -0.899,0.733 -1.767,1.45 -2.6,2.15 l -0.1,-0.049 z M 302.15,312.6 c -0.759,-0.891 -1.493,-1.791 -2.2,-2.7 -0.932,-1.2 -1.823,-2.417 -2.675,-3.65 -0.584,-0.868 -1.151,-1.743 -1.7,-2.625 -5.713,-9.105 -9.238,-19.13 -10.575,-30.075 l 2.2,-0.6 c 0.244,-0.06 0.494,-0.109 0.75,-0.15 H 288 c 0.633,-0.1 1.283,-0.15 1.95,-0.15 0.3,-0.066 0.617,-0.1 0.95,-0.1 0.434,0 0.867,0.034 1.3,0.1 0.501,0 0.968,0.05 1.4,0.15 h 0.025 c 0.946,7.487 3.054,14.487 6.325,21 0.793,1.581 1.651,3.131 2.575,4.65 0.569,0.929 1.161,1.845 1.775,2.75 1.07,1.567 2.211,3.101 3.425,4.6 -0.236,0.257 -0.469,0.515 -0.7,0.775 l -4.875,6.025 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#b6b6b6;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path22"
|
||||
d="m 309.9,425.325 c -0.893,-0.95 -1.776,-1.926 -2.65,-2.925 -1.955,-2.252 -3.78,-4.544 -5.475,-6.875 -12.172,-16.847 -17.497,-35.938 -15.975,-57.275 0.567,-7.866 1.983,-15.283 4.25,-22.25 0.534,-1.667 1.117,-3.3 1.75,-4.9 0.886,-2.133 1.836,-4.249 2.85,-6.35 0.017,-0.033 0.034,-0.066 0.05,-0.1 1.367,-2.634 2.85,-5.167 4.45,-7.601 0.6,-1 1.25,-2 1.95,-3 0.342,-0.488 0.692,-0.972 1.05,-1.45 l 4.875,-6.025 c 0.231,-0.26 0.464,-0.519 0.7,-0.775 1.816,-1.997 3.75,-3.931 5.8,-5.8 0.519,-0.471 1.044,-0.938 1.574,-1.4 0.167,-0.167 0.317,-0.3 0.45,-0.4 0.033,-0.1 0.101,-0.184 0.2,-0.25 3.934,-3.4 8.017,-6.383 12.25,-8.95 15.366,-9.367 32.533,-13.417 51.5,-12.15 h 0.05 c 0.601,0.033 3.55,0.4 8.851,1.1 0.1,0 0.25,0.017 0.449,0.05 1.534,0.267 3.017,0.567 4.45,0.9 0.2,0.033 0.417,0.083 0.65,0.15 9.905,2.289 19.021,6.189 27.35,11.7 -0.087,0.181 -0.179,0.364 -0.274,0.55 -7.707,-4.751 -16.065,-8.167 -25.075,-10.25 -0.233,-0.066 -0.45,-0.117 -0.65,-0.15 -1.434,-0.333 -2.916,-0.633 -4.45,-0.9 -0.199,-0.033 -0.35,-0.05 -0.449,-0.05 -5.301,-0.7 -8.25,-1.067 -8.851,-1.1 h -0.05 c -18.98,-1.281 -36.146,2.769 -51.5,12.15 -4.22,2.58 -8.303,5.563 -12.25,8.95 -0.1,0.066 -0.167,0.15 -0.2,0.25 -0.133,0.1 -0.283,0.233 -0.45,0.4 -5.433,4.733 -10.1,9.883 -14,15.45 -0.7,1 -1.35,2 -1.95,3 -1.6,2.434 -3.083,4.967 -4.45,7.601 -1.034,2.133 -2,4.283 -2.9,6.449 -0.634,1.608 -1.217,3.242 -1.75,4.9 -2.268,6.971 -3.685,14.388 -4.25,22.25 -1.451,20.351 3.324,38.659 14.325,54.925 2.144,3.148 4.519,6.224 7.125,9.226 0.08,0.091 0.163,0.183 0.25,0.274 1.057,1.209 2.132,2.384 3.225,3.525 -0.952,-0.93 -1.894,-1.889 -2.825,-2.874 z M 363.8,316.05 c 0.185,0.165 0.368,0.332 0.55,0.5 2.034,1.934 3.551,4.05 4.551,6.351 0.1,0.199 0.199,0.383 0.3,0.55 0.91,1.683 1.594,3.44 2.05,5.274 -0.523,0.094 -1.049,0.177 -1.575,0.25 -0.054,-0.359 -0.112,-0.718 -0.175,-1.074 -0.434,-2.267 -1.2,-4.417 -2.3,-6.45 -0.101,-0.167 -0.2,-0.351 -0.3,-0.55 -0.748,-1.718 -1.781,-3.335 -3.101,-4.851 z m -33.9,34.85 c 0.853,0.762 1.744,1.445 2.675,2.05 3.235,2.124 6.943,3.324 11.125,3.6 h 0.25 c 0.166,0 0.316,0.033 0.45,0.101 0.1,0 0.199,0 0.3,0 0.1,0 0.2,0 0.3,0 0.1,0 0.2,0 0.3,0 1.066,0 2.101,-0.051 3.101,-0.15 0.233,-0.066 0.483,-0.1 0.75,-0.1 L 352,355.8 c 2.5,-0.633 4.967,-1.85 7.4,-3.649 0.1,-0.101 0.199,-0.167 0.3,-0.2 0.434,-0.367 0.866,-0.733 1.3,-1.101 0.033,-0.066 0.1,-0.116 0.2,-0.149 0,-0.066 0.05,-0.117 0.149,-0.15 0.101,-0.133 0.184,-0.217 0.25,-0.25 3.351,-2.926 5.684,-6.342 7,-10.25 0.881,-0.08 1.756,-0.172 2.625,-0.274 -1.101,4.872 -3.643,9.047 -7.625,12.524 -0.066,0.033 -0.149,0.117 -0.25,0.25 -0.1,0.033 -0.149,0.084 -0.149,0.15 -0.101,0.033 -0.167,0.083 -0.2,0.149 -0.434,0.367 -0.866,0.733 -1.3,1.101 -0.101,0.033 -0.2,0.1 -0.3,0.2 -2.434,1.8 -4.9,3.017 -7.4,3.649 l -2.85,0.601 c -0.267,0 -0.517,0.033 -0.75,0.1 -1,0.1 -2.034,0.15 -3.101,0.15 -0.1,0 -0.2,0 -0.3,0 -0.1,0 -0.2,0 -0.3,0 -0.101,0 -0.2,0 -0.3,0 -0.134,-0.067 -0.284,-0.101 -0.45,-0.101 h -0.25 c -5.009,-0.33 -9.343,-1.98 -13,-4.95 -0.979,-0.805 -1.912,-1.705 -2.799,-2.7 z m 100.6,-53.175 c 1.706,1.48 3.372,3.038 5,4.675 0.208,0.212 0.416,0.429 0.625,0.65 -1.864,-1.801 -3.772,-3.501 -5.725,-5.1 0.03,-0.076 0.063,-0.151 0.1,-0.225 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#a3a3a3;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path24"
|
||||
d="m 462.475,367.475 -51.85,51.825 -33.95,33.925 c -4.175,0.269 -8.983,0.427 -14.425,0.476 -0.167,0 -0.3,0 -0.4,0 -12.399,-1.934 -23.616,-6.184 -33.649,-12.75 -3.066,-1.967 -5.983,-4.134 -8.75,-6.5 -2.308,-1.941 -4.549,-4.024 -6.725,-6.25 -1.093,-1.142 -2.168,-2.316 -3.225,-3.525 -0.087,-0.092 -0.17,-0.184 -0.25,-0.274 -2.606,-3.002 -4.981,-6.077 -7.125,-9.226 -11.001,-16.266 -15.776,-34.574 -14.325,-54.925 0.565,-7.862 1.982,-15.279 4.25,-22.25 0.533,-1.658 1.116,-3.292 1.75,-4.9 0.9,-2.166 1.867,-4.316 2.9,-6.449 1.367,-2.634 2.85,-5.167 4.45,-7.601 0.6,-1 1.25,-2 1.95,-3 3.9,-5.566 8.566,-10.716 14,-15.45 0.167,-0.167 0.317,-0.3 0.45,-0.4 0.033,-0.1 0.101,-0.184 0.2,-0.25 3.947,-3.387 8.03,-6.37 12.25,-8.95 15.354,-9.381 32.52,-13.431 51.5,-12.15 h 0.05 c 0.601,0.033 3.55,0.4 8.851,1.1 0.1,0 0.25,0.017 0.449,0.05 1.534,0.267 3.017,0.567 4.45,0.9 0.2,0.033 0.417,0.083 0.65,0.15 9.01,2.083 17.368,5.499 25.075,10.25 -3.247,6.607 -7.688,12.724 -13.325,18.35 -9.269,9.287 -19.877,15.336 -31.825,18.15 -1.519,0.363 -3.06,0.671 -4.625,0.925 -0.456,-1.834 -1.14,-3.592 -2.05,-5.274 -0.101,-0.167 -0.2,-0.351 -0.3,-0.55 -1,-2.301 -2.517,-4.417 -4.551,-6.351 -0.182,-0.168 -0.365,-0.335 -0.55,-0.5 -0.334,-0.288 -0.676,-0.563 -1.024,-0.825 -0.246,-0.19 -0.496,-0.374 -0.75,-0.55 -3.474,-2.444 -7.615,-3.87 -12.426,-4.275 -2.301,-0.139 -4.501,0.011 -6.6,0.45 -3.92,0.822 -7.487,2.656 -10.7,5.5 -0.233,0.167 -0.45,0.351 -0.649,0.551 -1.766,1.505 -3.249,3.154 -4.45,4.949 -0.377,0.57 -0.728,1.154 -1.05,1.75 l -0.051,0.125 c -0.578,1.09 -1.062,2.231 -1.449,3.426 -0.261,0.81 -0.478,1.643 -0.65,2.5 -0.019,0.091 -0.035,0.183 -0.05,0.274 -0.053,0.273 -0.104,0.548 -0.15,0.825 -0.114,0.75 -0.198,1.517 -0.25,2.3 0,0.033 0,0.084 0,0.15 -0.09,1.646 -0.04,3.246 0.15,4.8 0.082,0.744 0.199,1.478 0.35,2.2 0.834,3.666 2.601,7.066 5.3,10.2 0.018,0.016 0.034,0.032 0.051,0.05 h 0.1 c 0.133,0.155 0.266,0.306 0.4,0.45 0.887,0.994 1.819,1.895 2.8,2.699 3.657,2.97 7.991,4.62 13,4.95 h 0.25 c 0.166,0 0.316,0.033 0.45,0.101 0.1,0 0.199,0 0.3,0 0.1,0 0.2,0 0.3,0 0.1,0 0.2,0 0.3,0 1.066,0 2.101,-0.051 3.101,-0.15 0.233,-0.066 0.483,-0.1 0.75,-0.1 L 354,357.8 c 2.5,-0.633 4.967,-1.85 7.4,-3.649 0.1,-0.101 0.199,-0.167 0.3,-0.2 0.434,-0.367 0.866,-0.733 1.3,-1.101 0.033,-0.066 0.1,-0.116 0.2,-0.149 0,-0.066 0.05,-0.117 0.149,-0.15 0.101,-0.133 0.184,-0.217 0.25,-0.25 3.982,-3.478 6.524,-7.652 7.625,-12.524 2.564,-0.304 5.081,-0.729 7.551,-1.275 0.097,-0.021 0.196,-0.046 0.3,-0.075 13.821,-3.098 26.063,-9.989 36.725,-20.675 6.097,-6.083 10.964,-12.683 14.601,-19.8 1.952,1.598 3.86,3.298 5.725,5.1 0.461,0.441 0.919,0.892 1.375,1.35 1.5,1.533 2.983,3.15 4.45,4.85 1.399,1.667 2.767,3.35 4.1,5.05 7.5,9.934 12.584,20.617 15.25,32.05 0.566,2.601 1.05,5.217 1.45,7.851 0.033,0.467 0.033,0.883 0,1.25 0.284,3.789 0.192,7.798 -0.276,12.022 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#8f8f8f;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path26"
|
||||
d="m 474.175,453.9 h -0.024 v 0.05 c -0.367,0.066 -0.75,0.033 -1.15,-0.101 -0.192,-0.096 -0.359,-0.229 -0.5,-0.399 -0.115,-0.131 -0.216,-0.281 -0.3,-0.45 v -0.15 c -0.134,-0.333 -0.134,-0.683 0,-1.05 l -0.05,0.101 6.949,-25.551 c 0.101,-0.399 0.051,-0.816 -0.149,-1.25 -0.167,-0.366 -0.417,-0.683 -0.75,-0.949 -0.134,-0.034 -8.167,-8.784 -24.101,-26.25 1.967,-3.834 4.051,-10.117 6.25,-18.851 0.32,-1.295 0.612,-2.57 0.875,-3.825 -0.005,1.095 -0.005,2.328 0,3.7 0.032,6.125 -1.677,13.117 -5.125,20.976 15.934,17.466 23.967,26.216 24.101,26.25 0.333,0.267 0.583,0.583 0.75,0.949 0.2,0.434 0.25,0.851 0.149,1.25 l -5.975,21.976 -0.975,3.575 0.05,-0.101 c -0.012,0.032 -0.02,0.065 -0.025,0.1 z m 53.775,19.45 c 6.564,5.904 9.681,14.588 9.35,26.051 -0.467,13.733 -1.383,23.517 -2.75,29.35 -0.233,1.167 -0.767,2.25 -1.6,3.25 l -0.15,0.3 c -0.333,0.233 -0.649,0.45 -0.95,0.65 -0.8,0.533 -1.666,0.866 -2.6,1 -0.8,0.2 -1.684,0.399 -2.65,0.6 -5.933,1 -14.783,1.7 -26.55,2.101 -12.402,0.389 -21.669,-2.827 -27.8,-9.65 6.079,5.449 14.679,7.999 25.8,7.65 11.767,-0.4 20.617,-1.101 26.55,-2.101 0.967,-0.2 1.851,-0.399 2.65,-0.6 0.934,-0.134 1.8,-0.467 2.6,-1 0.301,-0.2 0.617,-0.417 0.95,-0.65 l 0.15,-0.3 c 0.833,-1 1.366,-2.083 1.6,-3.25 1.367,-5.833 2.283,-15.616 2.75,-29.35 0.296,-10.23 -2.154,-18.247 -7.35,-24.051 z m -31.125,3.225 c -0.357,0.039 -0.698,0.014 -1.025,-0.075 -0.333,-0.2 -0.6,-0.467 -0.8,-0.8 -0.066,-0.066 -0.1,-0.15 -0.1,-0.25 -0.134,-0.334 -0.15,-0.667 -0.051,-1 l 0.051,-0.05 6.75,-25.051 c 0.1,-0.433 0.083,-0.85 -0.051,-1.25 1.423,1.927 2.105,3.01 2.051,3.25 l -6.75,25.051 -0.051,0.05 c -0.011,0.039 -0.019,0.081 -0.024,0.125 z M 398.6,452.75 c -0.2,0.036 -0.399,0.069 -0.6,0.1 v -0.699 l 0.6,0.599 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#dbdbdb;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path28"
|
||||
d="m 410.625,419.3 51.85,-51.825 c -0.286,2.505 -0.703,5.089 -1.25,7.75 -0.263,1.255 -0.555,2.53 -0.875,3.825 -2.199,8.733 -4.283,15.017 -6.25,18.851 15.934,17.466 23.967,26.216 24.101,26.25 0.333,0.267 0.583,0.583 0.75,0.949 0.2,0.434 0.25,0.851 0.149,1.25 l -6.95,25.55 0.05,-0.101 c -0.134,0.367 -0.134,0.717 0,1.05 V 453 c 0.084,0.169 0.185,0.319 0.3,0.45 0.141,0.17 0.308,0.304 0.5,0.399 0.4,0.134 0.783,0.167 1.15,0.101 v -0.05 h 0.024 l 25.325,-6.65 c 0.434,-0.233 0.833,-0.217 1.2,0.05 0.333,0.167 0.633,0.434 0.899,0.8 0.134,0.4 0.15,0.817 0.051,1.25 l -6.749,25.05 -0.051,0.05 c -0.1,0.333 -0.083,0.666 0.051,1 0,0.1 0.033,0.184 0.1,0.25 0.2,0.333 0.467,0.6 0.8,0.8 0.327,0.089 0.668,0.114 1.025,0.075 0.039,-0.01 0.081,-0.018 0.125,-0.025 v 0.101 l 0.1,-0.101 26.65,-7 c 1.576,1.142 2.992,2.408 4.25,3.8 5.195,5.805 7.646,13.821 7.35,24.051 -0.467,13.733 -1.383,23.517 -2.75,29.35 -0.233,1.167 -0.767,2.25 -1.6,3.25 l -0.15,0.3 c -0.333,0.233 -0.649,0.45 -0.95,0.65 -0.8,0.533 -1.666,0.866 -2.6,1 -0.8,0.2 -1.684,0.399 -2.65,0.6 -21.5,-21.6 -32.816,-32.934 -33.949,-34 L 430.55,438.2 c -0.2,-0.101 -0.366,-0.233 -0.5,-0.4 l -5.2,-5.25 -0.149,0.15 c -0.101,0.066 -0.2,0.166 -0.3,0.3 -3.216,-3.303 -7.273,-7.353 -12.176,-12.15 -0.525,-0.505 -1.058,-1.022 -1.6,-1.55 z m 10.725,16.1 0.101,0.05 h -0.101 v -0.05 z"
|
||||
clip-rule="evenodd" />
|
||||
<path
|
||||
style="fill:#c8c8c8;fill-rule:evenodd"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path30"
|
||||
d="m 421.35,435.4 v 0.05 h 0.101 c 0.833,-0.7 1.7,-1.417 2.6,-2.15 0.066,-0.133 0.184,-0.233 0.351,-0.3 0.1,-0.134 0.199,-0.233 0.3,-0.3 l 0.149,-0.15 5.2,5.25 c 0.134,0.167 0.3,0.3 0.5,0.4 l 60.101,60.35 c 1.133,1.066 12.449,12.4 33.949,34 -9.066,-2.467 -16.033,-6.467 -20.899,-12 -4.934,-5.633 -8.283,-9.116 -10.05,-10.45 -1.801,-1.3 -2.817,-1.866 -3.051,-1.699 -0.199,0.267 -0.449,0.483 -0.75,0.649 -1.199,0.9 -2.616,1.384 -4.25,1.45 -1.933,0 -3.6,-0.684 -5,-2.05 l -1.1,-1.101 c -0.233,-0.233 -0.417,-0.416 -0.55,-0.55 l -65.601,-65.75 c 0.067,-0.066 0.15,-0.116 0.25,-0.149 1.134,-0.733 2.267,-1.467 3.4,-2.2 0.233,-0.134 0.483,-0.3 0.75,-0.5 0.134,-0.101 0.3,-0.217 0.5,-0.351 l 3.1,-2.449 z"
|
||||
clip-rule="evenodd" />
|
||||
</g></g>
|
||||
</svg>
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 25 KiB |
32
Graphics/drawables/yubi_icon.svg
Normal file
32
Graphics/drawables/yubi_icon.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="70"
|
||||
height="62.406281"
|
||||
id="svg2">
|
||||
<metadata
|
||||
id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs6" />
|
||||
<path
|
||||
d="M 52.3657,58.43801 C 47.458994,54.62787 46.155582,54.06779 43.398952,54.58493 40.399791,55.14758 38.763996,54.05889 20.086731,39.06961 7.730768,29.153433 0,22.274008 0,21.194906 0,18.644017 15.681185,0 17.826696,0 18.808287,0 28.361339,7.09013 39.055703,15.755845 56.31613,29.742087 58.53511,31.874951 58.81263,34.74603 c 0.24723,2.55776 1.45004,4.12268 5.75,7.48098 C 67.65157,44.63951 70,47.27165 70,48.32128 70,50.36282 61.61149,61.55137 59.46262,62.37597 58.717,62.66209 55.52339,60.89001 52.3657,58.43801 z m 7.98161,-4.04013 c 0.94475,-1.98117 2.6031,-3.83366 3.68521,-4.11664 3.08813,-0.80756 2.3486,-2.54551 -2.78252,-6.53917 L 56.5,40.04505 52.37071,44.7553 c -2.27111,2.59063 -3.93658,5.16799 -3.70104,5.72747 0.45926,1.09086 8.4545,7.47432 9.39512,7.50113 0.31063,0.009 1.33777,-1.60486 2.28252,-3.58602 z m 5.20186,-2.18204 c -0.85757,-0.85757 -3.55092,1.94357 -3.5159,3.6566 0.0257,1.25888 0.48444,1.0858 2.025,-0.7641 1.09546,-1.31542 1.76636,-2.61704 1.4909,-2.8925 z M 47.573574,48.4862 C 48.39799,46.74888 50.80267,43.52685 52.91732,41.32613 59.85163,34.10958 60.37768,34.99685 38.834338,17.572733 28.24449,9.00773 18.828918,2 17.910844,2 15.98216,2 2.014962,18.730529 2.005994,21.051535 c -0.0033,0.853344 8.785659,8.598609 19.531012,17.211695 18.295855,14.66531 19.695848,15.5878 22.037322,14.52095 1.375173,-0.62657 3.174834,-2.56066 3.999246,-4.29798 z M 22.153135,37.36544 C 11.618911,28.862306 3,21.435201 3,20.86076 3,20.286319 4.004197,18.603773 5.231549,17.121769 l 2.231549,-2.694552 1.518451,3.036391 C 10.291844,20.083758 11.046491,20.5 14.486525,20.5 c 3.40146,0 4.188035,-0.420435 5.35957,-2.864761 1.227741,-2.561596 1.165068,-3.143637 -0.592223,-5.5 C 18.172975,10.685858 16.131556,9.275 14.717385,9 L 12.146166,8.5 14.525117,5.75 C 15.83354,4.2375 17.403231,3 18.013319,3 19.191537,3 56.29941,32.61103 56.7872,33.940454 c 0.15796,0.430506 -1.05275,2.307516 -2.69047,4.171146 -6.574606,7.48151 -8.129309,9.45228 -8.591582,10.89088 -0.26554,0.82636 -1.318918,2.02463 -2.34084,2.66283 -1.618911,1.01103 -4.323025,-0.82935 -21.011173,-14.29987 z M 37.931436,36.25 C 44.595087,30.065353 40.901626,20 31.968531,20 28.745553,20 27.208412,20.637742 24.923077,22.923077 16.352,31.494154 29.044823,44.49781 37.931436,36.25 z M 25.923077,35.07692 C 22.66548,31.819326 22.293119,29.267638 24.463015,25.071521 26.128048,21.851703 27.789913,21 32.407473,21 c 8.048979,0 10.806778,11.769366 3.6277,15.48181 -4.304155,2.22577 -6.832743,1.87447 -10.112096,-1.40489 z M 36.92742,33.365141 C 39.649186,29.904977 39.544086,27.453177 36.545455,24.454545 33.546823,21.455914 31.095023,21.350814 27.634859,24.07258 25.860414,25.468358 25,27.10111 25,29.07258 c 0,6.47951 7.955148,9.34249 11.92742,4.292561 z M 30.666667,30.333333 C 29.516033,29.1827 29.938426,28 31.5,28 c 0.825,0 1.5,0.675 1.5,1.5 0,1.561574 -1.1827,1.983967 -2.333333,0.833333 z M 9.979785,17.962227 C 7.664953,13.636924 12.068603,8.798967 16.487721,10.812451 21.403259,13.052121 19.505702,20 13.97848,20 12.075855,20 10.693296,19.295435 9.979785,17.962227 z m 7.089782,-0.54605 C 18.846012,15.275691 17.059122,12.5 13.904709,12.5 c -2.721086,0 -3.747785,2.77923 -1.86484,5.048039 1.568288,1.889672 3.391646,1.84187 5.029698,-0.131862 z"
|
||||
id="path3031"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
@ -22,7 +22,7 @@ SRC_DIR=./drawables/
|
||||
#inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg
|
||||
|
||||
|
||||
for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "status_signature_verified_inner"
|
||||
for NAME in "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "status_signature_verified_inner"
|
||||
do
|
||||
echo $NAME
|
||||
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
||||
|
@ -5,7 +5,7 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
||||
classpath 'com.novoda:gradle-android-test-plugin:0.10.1'
|
||||
classpath 'com.novoda:gradle-android-test-plugin:0.10.4'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyActio
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@ -152,8 +153,8 @@ public class CertifyOperationTest {
|
||||
|
||||
@Test
|
||||
public void testCertifyId() throws Exception {
|
||||
CertifyOperation op = operationWithFakePassphraseCache(
|
||||
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1);
|
||||
CertifyOperation op = new CertifyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application), null, null);
|
||||
|
||||
{
|
||||
CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application)
|
||||
@ -164,8 +165,8 @@ public class CertifyOperationTest {
|
||||
|
||||
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
|
||||
actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(),
|
||||
mStaticRing2.getPublicKey().getUnorderedUserIds(), null));
|
||||
CertifyResult result = op.certify(actions, null);
|
||||
mStaticRing2.getPublicKey().getUnorderedUserIds()));
|
||||
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
|
||||
|
||||
Assert.assertTrue("certification must succeed", result.success());
|
||||
|
||||
@ -180,8 +181,8 @@ public class CertifyOperationTest {
|
||||
|
||||
@Test
|
||||
public void testCertifyAttribute() throws Exception {
|
||||
CertifyOperation op = operationWithFakePassphraseCache(
|
||||
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1);
|
||||
CertifyOperation op = new CertifyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application), null, null);
|
||||
|
||||
{
|
||||
CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application)
|
||||
@ -193,7 +194,7 @@ public class CertifyOperationTest {
|
||||
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
|
||||
actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), null,
|
||||
mStaticRing2.getPublicKey().getUnorderedUserAttributes()));
|
||||
CertifyResult result = op.certify(actions, null);
|
||||
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
|
||||
|
||||
Assert.assertTrue("certification must succeed", result.success());
|
||||
|
||||
@ -209,14 +210,14 @@ public class CertifyOperationTest {
|
||||
|
||||
@Test
|
||||
public void testCertifySelf() throws Exception {
|
||||
CertifyOperation op = operationWithFakePassphraseCache(
|
||||
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1);
|
||||
CertifyOperation op = new CertifyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application), null, null);
|
||||
|
||||
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
|
||||
actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(),
|
||||
mStaticRing2.getPublicKey().getUnorderedUserIds(), null));
|
||||
|
||||
CertifyResult result = op.certify(actions, null);
|
||||
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
|
||||
|
||||
Assert.assertFalse("certification with itself must fail!", result.success());
|
||||
Assert.assertTrue("error msg must be about self certification",
|
||||
@ -226,7 +227,8 @@ public class CertifyOperationTest {
|
||||
@Test
|
||||
public void testCertifyNonexistent() throws Exception {
|
||||
|
||||
CertifyOperation op = operationWithFakePassphraseCache(null, null, mKeyPhrase1);
|
||||
CertifyOperation op = new CertifyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application), null, null);
|
||||
|
||||
{
|
||||
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
|
||||
@ -234,7 +236,7 @@ public class CertifyOperationTest {
|
||||
uids.add("nonexistent");
|
||||
actions.add(new CertifyAction(1234L, uids, null));
|
||||
|
||||
CertifyResult result = op.certify(actions, null);
|
||||
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
|
||||
|
||||
Assert.assertFalse("certification of nonexistent key must fail", result.success());
|
||||
Assert.assertTrue("must contain error msg about not found",
|
||||
@ -246,7 +248,7 @@ public class CertifyOperationTest {
|
||||
actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(),
|
||||
mStaticRing2.getPublicKey().getUnorderedUserIds(), null));
|
||||
|
||||
CertifyResult result = op.certify(actions, null);
|
||||
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
|
||||
|
||||
Assert.assertFalse("certification of nonexistent key must fail", result.success());
|
||||
Assert.assertTrue("must contain error msg about not found",
|
||||
@ -255,29 +257,4 @@ public class CertifyOperationTest {
|
||||
|
||||
}
|
||||
|
||||
private CertifyOperation operationWithFakePassphraseCache(
|
||||
final Long checkMasterKeyId, final Long checkSubKeyId, final Passphrase passphrase) {
|
||||
|
||||
return new CertifyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application),
|
||||
null, null) {
|
||||
@Override
|
||||
public Passphrase getCachedPassphrase(long masterKeyId, long subKeyId)
|
||||
throws NoSecretKeyException {
|
||||
if (checkMasterKeyId != null) {
|
||||
Assert.assertEquals("requested passphrase should be for expected master key id",
|
||||
(long) checkMasterKeyId, masterKeyId);
|
||||
}
|
||||
if (checkSubKeyId != null) {
|
||||
Assert.assertEquals("requested passphrase should be for expected sub key id",
|
||||
(long) checkSubKeyId, subKeyId);
|
||||
}
|
||||
if (passphrase == null) {
|
||||
return null;
|
||||
}
|
||||
return passphrase;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,9 +27,12 @@ import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
@ -101,7 +104,7 @@ public class PromoteKeyOperationTest {
|
||||
PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application), null, null);
|
||||
|
||||
PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId());
|
||||
PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), null);
|
||||
|
||||
Assert.assertTrue("promotion must succeed", result.success());
|
||||
|
||||
@ -113,15 +116,35 @@ public class PromoteKeyOperationTest {
|
||||
Iterator<UncachedPublicKey> it = mStaticRing.getPublicKeys();
|
||||
while (it.hasNext()) {
|
||||
long keyId = it.next().getKeyId();
|
||||
Assert.assertEquals("all subkeys must be divert-to-card",
|
||||
Assert.assertEquals("all subkeys must be gnu dummy",
|
||||
SecretKeyType.GNU_DUMMY, ring.getSecretKeyType(keyId));
|
||||
}
|
||||
}
|
||||
|
||||
// second attempt should fail
|
||||
result = op.execute(mStaticRing.getMasterKeyId());
|
||||
Assert.assertFalse("promotion of secret key must fail", result.success());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPromoteDivert() throws Exception {
|
||||
PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application,
|
||||
new ProviderHelper(Robolectric.application), null, null);
|
||||
|
||||
byte[] aid = Hex.decode("D2760001240102000000012345670000");
|
||||
|
||||
PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), aid);
|
||||
|
||||
Assert.assertTrue("promotion must succeed", result.success());
|
||||
|
||||
{
|
||||
CanonicalizedSecretKeyRing ring = new ProviderHelper(Robolectric.application)
|
||||
.getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId());
|
||||
|
||||
for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
|
||||
Assert.assertEquals("all subkeys must be divert-to-card",
|
||||
SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
|
||||
Assert.assertArrayEquals("all subkeys must have correct iv",
|
||||
aid, key.getIv());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@ -48,7 +50,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@ -138,11 +139,11 @@ public class PgpEncryptDecryptTest {
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpSignEncryptInput b = new PgpSignEncryptInput();
|
||||
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
|
||||
b.setSymmetricPassphrase(mPassphrase);
|
||||
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
|
||||
|
||||
PgpSignEncryptResult result = op.execute(b, data, out);
|
||||
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
|
||||
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
|
||||
@ -159,8 +160,7 @@ public class PgpEncryptDecryptTest {
|
||||
new ProviderHelper(Robolectric.application),
|
||||
null, // new DummyPassphraseCache(mPassphrase, 0L),
|
||||
data, out);
|
||||
b.setPassphrase(mPassphrase);
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(mPassphrase));
|
||||
Assert.assertTrue("decryption must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -182,8 +182,8 @@ public class PgpEncryptDecryptTest {
|
||||
new ProviderHelper(Robolectric.application),
|
||||
null, // new DummyPassphraseCache(mPassphrase, 0L),
|
||||
data, out);
|
||||
b.setPassphrase(new Passphrase(Arrays.toString(mPassphrase.getCharArray()) + "x"));
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(
|
||||
new Passphrase(new String(mPassphrase.getCharArray()) + "x")));
|
||||
Assert.assertFalse("decryption must succeed", result.success());
|
||||
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
|
||||
Assert.assertNull("signature should be an error", result.getSignatureResult());
|
||||
@ -200,7 +200,7 @@ public class PgpEncryptDecryptTest {
|
||||
new ProviderHelper(Robolectric.application),
|
||||
null, // new DummyPassphraseCache(mPassphrase, 0L),
|
||||
data, out);
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertFalse("decryption must succeed", result.success());
|
||||
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
|
||||
Assert.assertNull("signature should be an error", result.getSignatureResult());
|
||||
@ -222,11 +222,11 @@ public class PgpEncryptDecryptTest {
|
||||
new ProviderHelper(Robolectric.application), null);
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
PgpSignEncryptInput b = new PgpSignEncryptInput();
|
||||
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
|
||||
|
||||
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
|
||||
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
|
||||
PgpSignEncryptResult result = op.execute(b, data, out);
|
||||
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
|
||||
ciphertext = out.toByteArray();
|
||||
@ -238,10 +238,8 @@ public class PgpEncryptDecryptTest {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null);
|
||||
b.setPassphrase(mKeyPhrase1);
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(mKeyPhrase1));
|
||||
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with provided passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -264,7 +262,7 @@ public class PgpEncryptDecryptTest {
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
|
||||
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null);
|
||||
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -279,11 +277,11 @@ public class PgpEncryptDecryptTest {
|
||||
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
|
||||
null, mStaticRing1.getMasterKeyId(), null);
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertFalse("decryption with no passphrase must return pending", result.success());
|
||||
Assert.assertTrue("decryption with no passphrase should return pending", result.isPending());
|
||||
Assert.assertEquals("decryption with no passphrase should return pending passphrase",
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, result.getResult());
|
||||
RequiredInputType.PASSPHRASE, result.getRequiredInputParcel().mType);
|
||||
}
|
||||
|
||||
}
|
||||
@ -303,14 +301,14 @@ public class PgpEncryptDecryptTest {
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpSignEncryptInput b = new PgpSignEncryptInput();
|
||||
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
|
||||
b.setEncryptionMasterKeyIds(new long[] {
|
||||
mStaticRing1.getMasterKeyId(),
|
||||
mStaticRing2.getMasterKeyId()
|
||||
});
|
||||
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
|
||||
|
||||
PgpSignEncryptResult result = op.execute(b, data, out);
|
||||
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
|
||||
ciphertext = out.toByteArray();
|
||||
@ -325,7 +323,7 @@ public class PgpEncryptDecryptTest {
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
|
||||
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null);
|
||||
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -343,7 +341,7 @@ public class PgpEncryptDecryptTest {
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
// allow only the second to decrypt
|
||||
HashSet<Long> allowed = new HashSet<Long>();
|
||||
HashSet<Long> allowed = new HashSet<>();
|
||||
allowed.add(mStaticRing2.getMasterKeyId());
|
||||
|
||||
// provide passphrase for the second, and check that the first is never asked for!
|
||||
@ -351,7 +349,7 @@ public class PgpEncryptDecryptTest {
|
||||
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
|
||||
b.setAllowedKeyIds(allowed);
|
||||
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -372,7 +370,7 @@ public class PgpEncryptDecryptTest {
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
|
||||
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
|
||||
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -395,7 +393,7 @@ public class PgpEncryptDecryptTest {
|
||||
new ProviderHelper(Robolectric.application), null);
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
PgpSignEncryptInput b = new PgpSignEncryptInput();
|
||||
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
|
||||
|
||||
b.setEncryptionMasterKeyIds(new long[] {
|
||||
mStaticRing1.getMasterKeyId(),
|
||||
@ -403,10 +401,9 @@ public class PgpEncryptDecryptTest {
|
||||
});
|
||||
b.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId());
|
||||
b.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1));
|
||||
b.setSignaturePassphrase(mKeyPhrase1);
|
||||
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
|
||||
|
||||
PgpSignEncryptResult result = op.execute(b, data, out);
|
||||
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(mKeyPhrase1), data, out);
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
|
||||
ciphertext = out.toByteArray();
|
||||
@ -421,7 +418,7 @@ public class PgpEncryptDecryptTest {
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
|
||||
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null);
|
||||
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -447,7 +444,7 @@ public class PgpEncryptDecryptTest {
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
|
||||
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
|
||||
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
|
||||
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
|
||||
out.toByteArray(), plaintext.getBytes());
|
||||
@ -477,14 +474,14 @@ public class PgpEncryptDecryptTest {
|
||||
new ProviderHelper(Robolectric.application), null);
|
||||
|
||||
InputData data = new InputData(in, in.available());
|
||||
PgpSignEncryptInput b = new PgpSignEncryptInput();
|
||||
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
|
||||
|
||||
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
|
||||
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
|
||||
// this only works with ascii armored output!
|
||||
b.setEnableAsciiArmorOutput(true);
|
||||
b.setCharset("iso-2022-jp");
|
||||
PgpSignEncryptResult result = op.execute(b, data, out);
|
||||
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
|
||||
Assert.assertTrue("encryption must succeed", result.success());
|
||||
|
||||
ciphertext = out.toByteArray();
|
||||
@ -497,8 +494,7 @@ public class PgpEncryptDecryptTest {
|
||||
InputData data = new InputData(in, in.available());
|
||||
|
||||
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null);
|
||||
b.setPassphrase(mKeyPhrase1);
|
||||
DecryptVerifyResult result = b.build().execute();
|
||||
DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(mKeyPhrase1));
|
||||
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
|
||||
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes",
|
||||
out.toByteArray(), plaindata);
|
||||
|
@ -49,6 +49,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringBuilder;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
@ -81,6 +82,8 @@ public class PgpKeyOperationTest {
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
|
||||
static CryptoInputParcel cryptoInput;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
@ -108,6 +111,9 @@ public class PgpKeyOperationTest {
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
|
||||
cryptoInput = new CryptoInputParcel(new Date(), passphrase);
|
||||
|
||||
}
|
||||
|
||||
@Before public void setUp() throws Exception {
|
||||
@ -300,9 +306,16 @@ public class PgpKeyOperationTest {
|
||||
if (badphrase.equals(passphrase)) {
|
||||
badphrase = new Passphrase("a");
|
||||
}
|
||||
parcel.mAddUserIds.add("allure");
|
||||
|
||||
assertModifyFailure("keyring modification with bad passphrase should fail",
|
||||
ring, parcel, badphrase, LogType.MSG_MF_UNLOCK_ERROR);
|
||||
ring, parcel, new CryptoInputParcel(badphrase), LogType.MSG_MF_UNLOCK_ERROR);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
assertModifyFailure("no-op should fail",
|
||||
ring, parcel, cryptoInput, LogType.MSG_MF_ERROR_NOOP);
|
||||
}
|
||||
|
||||
}
|
||||
@ -646,7 +659,7 @@ public class PgpKeyOperationTest {
|
||||
parcel.mRevokeSubKeys.add(123L);
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, cryptoInput, parcel).getRing();
|
||||
|
||||
Assert.assertNull("revoking a nonexistent subkey should fail", otherModified);
|
||||
|
||||
@ -657,7 +670,8 @@ public class PgpKeyOperationTest {
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(keyId);
|
||||
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB,
|
||||
new CryptoInputParcel(new Date(), passphrase));
|
||||
|
||||
Assert.assertEquals("no extra packets in original", 0, onlyA.size());
|
||||
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
|
||||
@ -777,7 +791,7 @@ public class PgpKeyOperationTest {
|
||||
{ // we should be able to change the stripped/divert status of subkeys without passphrase
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, null);
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, new CryptoInputParcel());
|
||||
Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertEquals("new packet should have GNU_DUMMY S2K type",
|
||||
@ -793,7 +807,7 @@ public class PgpKeyOperationTest {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, false, serial));
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, null);
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, new CryptoInputParcel());
|
||||
Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertEquals("new packet should have GNU_DUMMY S2K type",
|
||||
@ -970,12 +984,12 @@ public class PgpKeyOperationTest {
|
||||
Assert.assertEquals("signature type must be positive certification",
|
||||
PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
|
||||
|
||||
// make sure packets can be distinguished by timestamp
|
||||
Thread.sleep(1000);
|
||||
|
||||
// applying the same modification AGAIN should not add more certifications but drop those
|
||||
// as duplicates
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, passphrase, true, false);
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB,
|
||||
new CryptoInputParcel(new Date(), passphrase), true, false);
|
||||
|
||||
Assert.assertEquals("duplicate modification: one extra packet in original", 1, onlyA.size());
|
||||
Assert.assertEquals("duplicate modification: one extra packet in modified", 1, onlyB.size());
|
||||
@ -1039,8 +1053,7 @@ public class PgpKeyOperationTest {
|
||||
// change passphrase to empty
|
||||
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
|
||||
// note that canonicalization here necessarily strips the empty notation packet
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB,
|
||||
passphrase);
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput);
|
||||
|
||||
Assert.assertEquals("exactly three packets should have been modified (the secret keys)",
|
||||
3, onlyB.size());
|
||||
@ -1052,8 +1065,10 @@ public class PgpKeyOperationTest {
|
||||
|
||||
// modify keyring, change to non-empty passphrase
|
||||
Passphrase otherPassphrase = TestingUtils.genPassphrase(true);
|
||||
CryptoInputParcel otherCryptoInput = new CryptoInputParcel(otherPassphrase);
|
||||
parcel.mNewUnlock = new ChangeUnlockParcel(otherPassphrase);
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB, new Passphrase());
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB,
|
||||
new CryptoInputParcel(new Date(), new Passphrase()));
|
||||
|
||||
Assert.assertEquals("exactly three packets should have been modified (the secret keys)",
|
||||
3, onlyB.size());
|
||||
@ -1086,7 +1101,7 @@ public class PgpKeyOperationTest {
|
||||
// we should still be able to modify it (and change its passphrase) without errors
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, otherCryptoInput, parcel);
|
||||
Assert.assertTrue("key modification must succeed", result.success());
|
||||
Assert.assertFalse("log must not contain a warning",
|
||||
result.getLog().containsWarnings());
|
||||
@ -1102,7 +1117,7 @@ public class PgpKeyOperationTest {
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, otherPassphrase2);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(otherPassphrase2), parcel);
|
||||
Assert.assertTrue("key modification must succeed", result.success());
|
||||
Assert.assertTrue("log must contain a failed passphrase change warning",
|
||||
result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_FAIL));
|
||||
@ -1140,7 +1155,7 @@ public class PgpKeyOperationTest {
|
||||
|
||||
{
|
||||
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase("phrayse"), null);
|
||||
applyModificationWithChecks(parcel, modified, onlyA, onlyB, pin, true, false);
|
||||
applyModificationWithChecks(parcel, modified, onlyA, onlyB, new CryptoInputParcel(pin), true, false);
|
||||
|
||||
Assert.assertEquals("exactly four packets should have been removed (the secret keys + notation packet)",
|
||||
4, onlyA.size());
|
||||
@ -1157,7 +1172,7 @@ public class PgpKeyOperationTest {
|
||||
|
||||
parcel.mAddUserIds.add("discord");
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, null);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Date()), parcel);
|
||||
Assert.assertFalse("non-restricted operations should fail without passphrase", result.success());
|
||||
}
|
||||
|
||||
@ -1165,15 +1180,15 @@ public class PgpKeyOperationTest {
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB) {
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true);
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput, true, true);
|
||||
}
|
||||
|
||||
private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB,
|
||||
Passphrase passphrase) {
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true);
|
||||
CryptoInputParcel cryptoInput) {
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput, true, true);
|
||||
}
|
||||
|
||||
// applies a parcel modification while running some integrity checks
|
||||
@ -1181,7 +1196,7 @@ public class PgpKeyOperationTest {
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB,
|
||||
Passphrase passphrase,
|
||||
CryptoInputParcel cryptoInput,
|
||||
boolean canonicalize,
|
||||
boolean constantCanonicalize) {
|
||||
|
||||
@ -1191,7 +1206,7 @@ public class PgpKeyOperationTest {
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel);
|
||||
Assert.assertTrue("key modification must succeed", result.success());
|
||||
UncachedKeyRing rawModified = result.getRing();
|
||||
Assert.assertNotNull("key modification must not return null", rawModified);
|
||||
@ -1258,11 +1273,11 @@ public class PgpKeyOperationTest {
|
||||
}
|
||||
|
||||
private void assertModifyFailure(String reason, UncachedKeyRing ring,
|
||||
SaveKeyringParcel parcel, Passphrase passphrase, LogType expected)
|
||||
SaveKeyringParcel parcel, CryptoInputParcel cryptoInput, LogType expected)
|
||||
throws Exception {
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel);
|
||||
|
||||
Assert.assertFalse(reason, result.success());
|
||||
Assert.assertNull(reason, result.getRing());
|
||||
@ -1276,7 +1291,7 @@ public class PgpKeyOperationTest {
|
||||
throws Exception {
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, parcel, passphrase);
|
||||
PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel);
|
||||
|
||||
Assert.assertFalse(reason, result.success());
|
||||
Assert.assertNull(reason, result.getRing());
|
||||
|
@ -59,6 +59,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@ -549,7 +550,10 @@ public class UncachedKeyringCanonicalizeTest {
|
||||
CanonicalizedSecretKey masterSecretKey = canonicalized.getSecretKey();
|
||||
masterSecretKey.unlock(new Passphrase());
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
CryptoInputParcel cryptoInput = new CryptoInputParcel();
|
||||
PGPSignature cert = PgpKeyOperation.generateSubkeyBindingSignature(
|
||||
PgpKeyOperation.getSignatureGenerator(masterSecretKey.getSecretKey(), cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterSecretKey.getPrivateKey(), masterSecretKey.getPrivateKey(),
|
||||
masterPublicKey, masterSecretKey.getKeyUsage(), 0);
|
||||
PGPPublicKey subPubKey = PGPPublicKey.addSubkeyBindingCertification(masterPublicKey, cert);
|
||||
|
@ -35,9 +35,12 @@ import org.spongycastle.util.Strings;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@ -46,6 +49,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
|
||||
@ -186,11 +190,11 @@ public class UncachedKeyringMergeTest {
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("flim");
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing();
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("flam");
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing();
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
|
||||
}
|
||||
|
||||
{ // merge A into base
|
||||
@ -227,8 +231,8 @@ public class UncachedKeyringMergeTest {
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing();
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing();
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
|
||||
|
||||
subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2);
|
||||
subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2);
|
||||
@ -269,7 +273,7 @@ public class UncachedKeyringMergeTest {
|
||||
parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1));
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
|
||||
ringA.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing();
|
||||
modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
|
||||
}
|
||||
|
||||
{
|
||||
@ -295,8 +299,13 @@ public class UncachedKeyringMergeTest {
|
||||
CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing(
|
||||
ringB.getEncoded(), false, 0).getSecretKey();
|
||||
secretKey.unlock(new Passphrase());
|
||||
PgpCertifyOperation op = new PgpCertifyOperation();
|
||||
CertifyAction action = new CertifyAction(pubRing.getMasterKeyId(), publicRing.getPublicKey().getUnorderedUserIds());
|
||||
// sign all user ids
|
||||
modified = secretKey.certifyUserIds(publicRing, publicRing.getPublicKey().getUnorderedUserIds(), null, null);
|
||||
PgpCertifyResult result = op.certify(secretKey, publicRing, new OperationLog(), 0, action, null, new Date());
|
||||
Assert.assertTrue("certification must succeed", result.success());
|
||||
Assert.assertNotNull("certification must yield result", result.getCertifiedRing());
|
||||
modified = result.getCertifiedRing();
|
||||
}
|
||||
|
||||
{
|
||||
@ -363,7 +372,7 @@ public class UncachedKeyringMergeTest {
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
|
||||
ringA.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing();
|
||||
modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -13,27 +13,27 @@ dependencies {
|
||||
|
||||
// JCenter etc.
|
||||
compile 'com.eftimoff:android-patternview:1.0.1@aar'
|
||||
compile 'com.journeyapps:zxing-android-embedded:2.1.0@aar'
|
||||
compile 'com.journeyapps:zxing-android-integration:2.1.0@aar'
|
||||
compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar'
|
||||
compile 'com.journeyapps:zxing-android-integration:2.3.0@aar'
|
||||
compile 'com.google.zxing:core:3.2.0'
|
||||
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||
compile 'it.neokree:MaterialNavigationDrawer:1.3.2'
|
||||
compile 'com.getbase:floatingactionbutton:1.9.0'
|
||||
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
|
||||
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
|
||||
compile "com.splitwise:tokenautocomplete:1.3.3@aar"
|
||||
compile 'se.emilsjolander:stickylistheaders:2.6.0'
|
||||
compile 'org.sufficientlysecure:html-textview:1.1'
|
||||
|
||||
// libs as submodules
|
||||
compile project(':extern:openpgp-api-lib')
|
||||
compile project(':extern:openkeychain-api-lib')
|
||||
compile project(':extern:html-textview')
|
||||
compile project(':extern:StickyListHeaders:library')
|
||||
compile project(':extern:spongycastle:core')
|
||||
compile project(':extern:spongycastle:pg')
|
||||
compile project(':extern:spongycastle:pkix')
|
||||
compile project(':extern:spongycastle:prov')
|
||||
compile project(':extern:minidns')
|
||||
compile project(':extern:KeybaseLib:Lib')
|
||||
compile project(':extern:TokenAutoComplete:library')
|
||||
compile project(':extern:safeslinger-exchange')
|
||||
compile project(':extern:snackbar:lib')
|
||||
}
|
||||
@ -47,24 +47,24 @@ dependencyVerification {
|
||||
'com.android.support:recyclerview-v7:859ed80e3761f8fc3126901260b208505120b5678bcf36ad2cfe9c453958b9c7',
|
||||
'com.android.support:cardview-v7:4c03f2acce9925aa4f8845cb8cb37b3772c712b2438ff15f76c9e3d3bc63ead7',
|
||||
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
|
||||
'com.journeyapps:zxing-android-embedded:57fdf8a262135976201fd89f1bd8016ed16510be92e7ea721b999daeeeab8f7e',
|
||||
'com.journeyapps:zxing-android-integration:12caeb2608f11b6df77d27edc505ac8580abfc97a09a814b638cb9df0ba06906',
|
||||
'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718',
|
||||
'com.journeyapps:zxing-android-integration:562737821b6d34c899b6fd2234ce0a8a31e02ff1fd7c59f6211961ce9767c7c8',
|
||||
'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b',
|
||||
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
|
||||
'it.neokree:MaterialNavigationDrawer:a1221a410c5f71bf078c5c4768fdf06b402d6006c74f8e7b61199e4edc2aea57',
|
||||
'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca',
|
||||
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
|
||||
'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb',
|
||||
'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4',
|
||||
'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988',
|
||||
// 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580',
|
||||
// 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429',
|
||||
// 'OpenKeychain.extern:html-textview:536822e8fdcd3e4628d0a1cd6c252285ba5f8e5bfb20d71ff80fdbdb6cc8be8c',
|
||||
// 'OpenKeychain.extern.StickyListHeaders:library:d9937cf9d9992863e32cee1f18ffec12df7b97dd83939bb75ee6cf747c54bed1',
|
||||
// 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b',
|
||||
// 'com.madgag.spongycastle:pg:160b345b10a2c92dc731453eec87037377f66a8e14a0648d404d7b193c4e380d',
|
||||
// 'com.madgag.spongycastle:pkix:0b4f3301ea12dd9f25d71770e6ea9f75e0611bf53062543e47be5bc15340a7e4',
|
||||
// 'com.madgag.spongycastle:prov:7325942e0b39f5fb35d6380818eed4b826e7dfc7570ad35b696d778049d8c36a',
|
||||
// 'OpenKeychain.extern:minidns:77b1786d29469e3b21f9404827cab811edc857cd68bc732cd57f11307c332eae',
|
||||
// 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf',
|
||||
// 'OpenKeychain.extern.TokenAutoComplete:library:9333f1c269996812baa18c0494e42f931309ab00a3cdb65a6e4d70f82d4c7107',
|
||||
// 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f',
|
||||
// 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23',
|
||||
'com.android.support:support-annotations:ab6b131ab0e1edd165d21fb4c3edadeacbee9539aa166f7f7cbae05b60dc207a',
|
||||
|
@ -90,6 +90,8 @@
|
||||
android:name=".ui.CreateKeyActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:label="@string/title_manage_my_keys"
|
||||
android:launchMode="singleTop"
|
||||
android:allowTaskReparenting="true"
|
||||
android:parentActivityName=".ui.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@ -673,7 +675,7 @@
|
||||
taskAffinity and allowTaskReparenting somehow prevents this from happening!
|
||||
-->
|
||||
<activity
|
||||
android:name=".ui.NfcActivity"
|
||||
android:name=".ui.NfcOperationActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=":Nfc"
|
||||
android:allowTaskReparenting="true" />
|
||||
@ -698,6 +700,10 @@
|
||||
android:name=".service.PassphraseCacheService"
|
||||
android:exported="false"
|
||||
android:process=":passphrase_cache" />
|
||||
<service
|
||||
android:name=".remote.CryptoInputParcelCacheService"
|
||||
android:exported="false"
|
||||
android:process=":remote_api" />
|
||||
<service
|
||||
android:name=".service.KeychainIntentService"
|
||||
android:exported="false" />
|
||||
|
@ -14,8 +14,12 @@ import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Provider;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This class is based on JcaPGPContentSignerBuilder.
|
||||
@ -31,31 +35,27 @@ public class NfcSyncPGPContentSignerBuilder
|
||||
private int keyAlgorithm;
|
||||
private long keyID;
|
||||
|
||||
private byte[] signedHash;
|
||||
private Date creationTimestamp;
|
||||
private Map signedHashes;
|
||||
|
||||
public static class NfcInteractionNeeded extends RuntimeException
|
||||
{
|
||||
public byte[] hashToSign;
|
||||
public Date creationTimestamp;
|
||||
public int hashAlgo;
|
||||
|
||||
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo, Date creationTimestamp)
|
||||
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo)
|
||||
{
|
||||
super("NFC interaction required!");
|
||||
this.hashToSign = hashToSign;
|
||||
this.hashAlgo = hashAlgo;
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, byte[] signedHash, Date creationTimestamp)
|
||||
public NfcSyncPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm, long keyID, Map signedHashes)
|
||||
{
|
||||
this.keyAlgorithm = keyAlgorithm;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.keyID = keyID;
|
||||
this.signedHash = signedHash;
|
||||
this.creationTimestamp = creationTimestamp;
|
||||
this.signedHashes = signedHashes;
|
||||
}
|
||||
|
||||
public NfcSyncPGPContentSignerBuilder setProvider(Provider provider)
|
||||
@ -125,14 +125,14 @@ public class NfcSyncPGPContentSignerBuilder
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
if (signedHash != null) {
|
||||
// we already have the signed hash from a previous execution, return this!
|
||||
return signedHash;
|
||||
} else {
|
||||
// catch this when signatureGenerator.generate() is executed and divert digest to card,
|
||||
// when doing the operation again reuse creationTimestamp (this will be hashed)
|
||||
throw new NfcInteractionNeeded(digestCalculator.getDigest(), getHashAlgorithm(), creationTimestamp);
|
||||
byte[] digest = digestCalculator.getDigest();
|
||||
ByteBuffer buf = ByteBuffer.wrap(digest);
|
||||
if (signedHashes.containsKey(buf)) {
|
||||
return (byte[]) signedHashes.get(buf);
|
||||
}
|
||||
// catch this when signatureGenerator.generate() is executed and divert digest to card,
|
||||
// when doing the operation again reuse creationTimestamp (this will be hashed)
|
||||
throw new NfcInteractionNeeded(digest, getHashAlgorithm());
|
||||
}
|
||||
|
||||
public byte[] getDigest()
|
||||
|
@ -15,7 +15,10 @@ import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.operator.PGPDataDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Provider;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This class is based on JcePublicKeyDataDecryptorFactoryBuilder
|
||||
@ -88,7 +91,7 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public PublicKeyDataDecryptorFactory build(final byte[] nfcDecrypted) {
|
||||
public PublicKeyDataDecryptorFactory build(final Map<ByteBuffer,byte[]> nfcDecryptedMap) {
|
||||
return new PublicKeyDataDecryptorFactory()
|
||||
{
|
||||
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
|
||||
@ -99,7 +102,7 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
throw new PGPException("ECDH not supported!");
|
||||
}
|
||||
|
||||
return decryptSessionData(keyAlgorithm, secKeyData, nfcDecrypted);
|
||||
return decryptSessionData(keyAlgorithm, secKeyData, nfcDecryptedMap);
|
||||
}
|
||||
|
||||
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
|
||||
@ -197,8 +200,9 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
// }
|
||||
// }
|
||||
|
||||
private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData, byte[] nfcDecrypted)
|
||||
throws PGPException
|
||||
private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData,
|
||||
Map<ByteBuffer,byte[]> nfcDecryptedMap)
|
||||
throws PGPException
|
||||
{
|
||||
// Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
|
||||
//
|
||||
@ -214,15 +218,14 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
|
||||
if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
|
||||
|| keyAlgorithm == PGPPublicKey.RSA_GENERAL)
|
||||
{
|
||||
byte[] bi = secKeyData[0]; // encoded MPI
|
||||
ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI
|
||||
|
||||
if (nfcDecrypted != null) {
|
||||
// we already have the decrypted bytes from a previous execution, return this!
|
||||
return nfcDecrypted;
|
||||
if (nfcDecryptedMap.containsKey(bi)) {
|
||||
return nfcDecryptedMap.get(bi);
|
||||
} else {
|
||||
// catch this when decryptSessionData() is executed and divert digest to card,
|
||||
// when doing the operation again reuse nfcDecrypted
|
||||
throw new NfcInteractionNeeded(bi);
|
||||
throw new NfcInteractionNeeded(bi.array());
|
||||
}
|
||||
|
||||
// c1.update(bi, 2, bi.length - 2);
|
||||
|
@ -28,8 +28,9 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
@ -38,6 +39,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@ -60,7 +64,7 @@ public class CertifyOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public CertifyResult certify(CertifyActionsParcel parcel, String keyServerUri) {
|
||||
public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_CRT, 0);
|
||||
@ -74,13 +78,14 @@ public class CertifyOperation extends BaseOperation {
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
|
||||
log.add(LogType.MSG_CRT_UNLOCK, 1);
|
||||
certificationKey = secretKeyRing.getSecretKey();
|
||||
if (certificationKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
|
||||
log.add(LogType.MSG_CRT_ERROR_DIVERT, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
|
||||
if (!cryptoInput.hasPassphrase()) {
|
||||
return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
|
||||
}
|
||||
|
||||
// certification is always with the master key id, so use that one
|
||||
Passphrase passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId);
|
||||
Passphrase passphrase = cryptoInput.getPassphrase();
|
||||
|
||||
if (!certificationKey.unlock(passphrase)) {
|
||||
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
|
||||
@ -92,9 +97,6 @@ public class CertifyOperation extends BaseOperation {
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
} catch (NoSecretKeyException e) {
|
||||
log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<>();
|
||||
@ -103,6 +105,10 @@ public class CertifyOperation extends BaseOperation {
|
||||
|
||||
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
|
||||
|
||||
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
|
||||
cryptoInput.getSignatureTime(), certificationKey.getKeyId(),
|
||||
certificationKey.getKeyId());
|
||||
|
||||
// Work through all requested certifications
|
||||
for (CertifyAction action : parcel.mCertifyActions) {
|
||||
|
||||
@ -123,28 +129,21 @@ public class CertifyOperation extends BaseOperation {
|
||||
CanonicalizedPublicKeyRing publicRing =
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId);
|
||||
|
||||
UncachedKeyRing certifiedKey = null;
|
||||
if (action.mUserIds != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
PgpCertifyOperation op = new PgpCertifyOperation();
|
||||
PgpCertifyResult result = op.certify(certificationKey, publicRing,
|
||||
log, 2, action, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
|
||||
|
||||
certifiedKey = certificationKey.certifyUserIds(
|
||||
publicRing, action.mUserIds, null, null);
|
||||
}
|
||||
|
||||
if (action.mUserAttributes != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
|
||||
certifiedKey = certificationKey.certifyUserAttributes(
|
||||
publicRing, action.mUserAttributes, null, null);
|
||||
}
|
||||
|
||||
if (certifiedKey == null) {
|
||||
if (!result.success()) {
|
||||
certifyError += 1;
|
||||
log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3);
|
||||
continue;
|
||||
}
|
||||
certifiedKeys.add(certifiedKey);
|
||||
if (result.nfcInputRequired()) {
|
||||
RequiredInputParcel requiredInput = result.getRequiredInput();
|
||||
allRequiredInput.addAll(requiredInput);
|
||||
continue;
|
||||
}
|
||||
|
||||
certifiedKeys.add(result.getCertifiedRing());
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
certifyError += 1;
|
||||
@ -153,6 +152,11 @@ public class CertifyOperation extends BaseOperation {
|
||||
|
||||
}
|
||||
|
||||
if ( ! allRequiredInput.isEmpty()) {
|
||||
log.add(LogType.MSG_CRT_NFC_RETURN, 1);
|
||||
return new CertifyResult(log, allRequiredInput.build());
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_CRT_SAVING, 1);
|
||||
|
||||
// Check if we were cancelled
|
||||
|
@ -21,6 +21,7 @@ import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||
@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
|
||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@ -56,7 +58,7 @@ public class EditKeyOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public EditKeyResult execute(SaveKeyringParcel saveParcel, Passphrase passphrase) {
|
||||
public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_ED, 0);
|
||||
@ -81,7 +83,10 @@ public class EditKeyOperation extends BaseOperation {
|
||||
CanonicalizedSecretKeyRing secRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
|
||||
if (modifyResult.isPending()) {
|
||||
return modifyResult;
|
||||
}
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2);
|
||||
|
@ -230,7 +230,7 @@ public class ImportExportOperation extends BaseOperation {
|
||||
}
|
||||
} catch (Keyserver.QueryFailedException e) {
|
||||
Log.e(Constants.TAG, "query failed", e);
|
||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3);
|
||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class PromoteKeyOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public PromoteKeyResult execute(long masterKeyId) {
|
||||
public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_PR, 0);
|
||||
@ -58,27 +58,16 @@ public class PromoteKeyOperation extends BaseOperation {
|
||||
// Perform actual type change
|
||||
UncachedKeyRing promotedRing;
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
// This operation is only allowed for pure public keys
|
||||
// TODO delete secret keys if they are stripped, or have been moved to the card?
|
||||
if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) {
|
||||
log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_PR_FETCHING, 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
|
||||
CanonicalizedPublicKeyRing pubRing =
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
// create divert-to-card secret key from public key
|
||||
promotedRing = pubRing.createDummySecretRing();
|
||||
promotedRing = pubRing.createDivertSecretRing(cardAid);
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
|
@ -20,14 +20,21 @@ package org.sufficientlysecure.keychain.operations;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
@ -55,7 +62,7 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public SignEncryptResult execute(SignEncryptParcel input) {
|
||||
public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_SE, 0);
|
||||
@ -68,6 +75,21 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
|
||||
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
|
||||
|
||||
NfcSignOperationsBuilder pendingInputBuilder = null;
|
||||
|
||||
// if signing subkey has not explicitly been set, get first usable subkey capable of signing
|
||||
if (input.getSignatureMasterKeyId() != Constants.key.none
|
||||
&& input.getSignatureSubKeyId() == null) {
|
||||
try {
|
||||
long signKeyId = mProviderHelper.getCachedPublicKeyRing(
|
||||
input.getSignatureMasterKeyId()).getSecretSignId();
|
||||
input.setSignatureSubKeyId(signKeyId);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
if (checkCancelled()) {
|
||||
@ -123,15 +145,22 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper,
|
||||
new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled);
|
||||
PgpSignEncryptResult result = op.execute(input, inputData, outStream);
|
||||
PgpSignEncryptResult result = op.execute(input, cryptoInput, inputData, outStream);
|
||||
results.add(result);
|
||||
log.add(result, 2);
|
||||
|
||||
if (result.isPending()) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_PENDING, log, results);
|
||||
}
|
||||
|
||||
if (!result.success()) {
|
||||
RequiredInputParcel requiredInput = result.getRequiredInputParcel();
|
||||
// Passphrase returns immediately, nfc are aggregated
|
||||
if (requiredInput.mType == RequiredInputType.PASSPHRASE) {
|
||||
return new SignEncryptResult(log, requiredInput, results);
|
||||
}
|
||||
if (pendingInputBuilder == null) {
|
||||
pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime,
|
||||
input.getSignatureMasterKeyId(), input.getSignatureSubKeyId());
|
||||
}
|
||||
pendingInputBuilder.addAll(requiredInput);
|
||||
} else if (!result.success()) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
|
||||
@ -141,9 +170,12 @@ public class SignEncryptOperation extends BaseOperation {
|
||||
|
||||
} while (!inputUris.isEmpty());
|
||||
|
||||
if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) {
|
||||
return new SignEncryptResult(log, pendingInputBuilder.build(), results);
|
||||
}
|
||||
|
||||
if (!outputUris.isEmpty()) {
|
||||
// Any output URIs left are indicative of a programming error
|
||||
log.add(LogType.MSG_SE_WARN_OUTPUT_LEFT, 1);
|
||||
throw new AssertionError("Got outputs left but no inputs. This is a programming error, please report!");
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_SE_SUCCESS, 1);
|
||||
|
@ -23,6 +23,7 @@ import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
@ -30,16 +31,19 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
public class CertifyResult extends OperationResult {
|
||||
|
||||
public class CertifyResult extends InputPendingResult {
|
||||
int mCertifyOk, mCertifyError, mUploadOk, mUploadError;
|
||||
|
||||
public CertifyResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public CertifyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
}
|
||||
|
||||
public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) {
|
||||
this(result, log);
|
||||
super(result, log);
|
||||
mCertifyOk = certifyOk;
|
||||
mCertifyError = certifyError;
|
||||
mUploadOk = uploadOk;
|
||||
|
@ -22,23 +22,10 @@ import android.os.Parcel;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class DecryptVerifyResult extends OperationResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
// fifth to sixth bit in addition indicate specific type of pending
|
||||
public static final int RESULT_PENDING_ASYM_PASSPHRASE = RESULT_PENDING + 16;
|
||||
public static final int RESULT_PENDING_SYM_PASSPHRASE = RESULT_PENDING + 32;
|
||||
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 64;
|
||||
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
long mNfcSubKeyId;
|
||||
byte[] mNfcSessionKey;
|
||||
Passphrase mNfcPassphrase;
|
||||
public class DecryptVerifyResult extends InputPendingResult {
|
||||
|
||||
OpenPgpSignatureResult mSignatureResult;
|
||||
OpenPgpMetadata mDecryptMetadata;
|
||||
@ -46,32 +33,6 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
// https://tools.ietf.org/html/rfc4880#page56
|
||||
String mCharset;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
|
||||
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setNfcState(long subKeyId, byte[] sessionKey, Passphrase passphrase) {
|
||||
mNfcSubKeyId = subKeyId;
|
||||
mNfcSessionKey = sessionKey;
|
||||
mNfcPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public long getNfcSubKeyId() {
|
||||
return mNfcSubKeyId;
|
||||
}
|
||||
|
||||
public byte[] getNfcEncryptedSessionKey() {
|
||||
return mNfcSessionKey;
|
||||
}
|
||||
|
||||
public Passphrase getNfcPassphrase() {
|
||||
return mNfcPassphrase;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult getSignatureResult() {
|
||||
return mSignatureResult;
|
||||
}
|
||||
@ -104,13 +65,14 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
}
|
||||
|
||||
public DecryptVerifyResult(Parcel source) {
|
||||
super(source);
|
||||
mKeyIdPassphraseNeeded = source.readLong();
|
||||
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
||||
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
|
||||
mNfcSessionKey = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
mNfcPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
@ -119,16 +81,8 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mKeyIdPassphraseNeeded);
|
||||
dest.writeParcelable(mSignatureResult, 0);
|
||||
dest.writeParcelable(mDecryptMetadata, 0);
|
||||
if (mNfcSessionKey != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mNfcSessionKey);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeParcelable(mNfcPassphrase, flags);
|
||||
}
|
||||
|
||||
public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
|
||||
|
@ -31,13 +31,18 @@ public class EditKeyResult extends OperationResult {
|
||||
|
||||
public EditKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mMasterKeyId = source.readLong();
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
if (mMasterKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Creator<EditKeyResult> CREATOR = new Creator<EditKeyResult>() {
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
public class InputPendingResult extends OperationResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
final RequiredInputParcel mRequiredInput;
|
||||
|
||||
public InputPendingResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
mRequiredInput = null;
|
||||
}
|
||||
|
||||
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(RESULT_PENDING, log);
|
||||
mRequiredInput = requiredInput;
|
||||
}
|
||||
|
||||
public InputPendingResult(Parcel source) {
|
||||
super(source);
|
||||
mRequiredInput = source.readParcelable(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeParcelable(mRequiredInput, 0);
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
public RequiredInputParcel getRequiredInputParcel() {
|
||||
return mRequiredInput;
|
||||
}
|
||||
|
||||
}
|
@ -33,75 +33,33 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableCache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** Represent the result of an operation.
|
||||
/**
|
||||
* Represent the result of an operation.
|
||||
*
|
||||
* This class holds a result and the log of an operation. It can be subclassed
|
||||
* to include typed additional information specific to the operation. To keep
|
||||
* the class structure (somewhat) simple, this class contains an exhaustive
|
||||
* list (ie, enum) of all possible log types, which should in all cases be tied
|
||||
* to string resource ids.
|
||||
*
|
||||
*/
|
||||
public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public static final String EXTRA_RESULT = "operation_result";
|
||||
public static final UUID NULL_UUID = new UUID(0,0);
|
||||
|
||||
/**
|
||||
* A HashMap of UUID:OperationLog which contains logs that we don't need
|
||||
* to care about. This is used such that when we become parceled, we are
|
||||
* well below the 1Mbit boundary that is specified.
|
||||
* Instead of parceling the logs, they are cached to overcome the 1 MB boundary of
|
||||
* Android's Binder. See ParcelableCache
|
||||
*/
|
||||
private static ConcurrentHashMap<UUID, OperationLog> dehydratedLogs;
|
||||
private static ParcelableCache<OperationLog> logCache;
|
||||
static {
|
||||
// Static initializer for ConcurrentHashMap
|
||||
dehydratedLogs = new ConcurrentHashMap<UUID,OperationLog>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dehydrate a log (such that it is available after deparcelization)
|
||||
*
|
||||
* Returns the NULL uuid (0) if you hand it null.
|
||||
* @param log An OperationLog to dehydrate
|
||||
* @return a UUID, the ticket for your dehydrated log
|
||||
*
|
||||
*/
|
||||
private static UUID dehydrateLog(OperationLog log) {
|
||||
if(log == null) {
|
||||
return NULL_UUID;
|
||||
}
|
||||
else {
|
||||
UUID ticket = UUID.randomUUID();
|
||||
dehydratedLogs.put(ticket, log);
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Rehydrate a log after going through parcelization, invalidating its place in the
|
||||
* dehydration pool.
|
||||
* This is used such that when parcelized, the parcel is no larger than 1mbit.
|
||||
* @param ticket A UUID ticket that identifies the log in question.
|
||||
* @return An OperationLog.
|
||||
*/
|
||||
private static OperationLog rehydrateLog(UUID ticket) {
|
||||
// UUID.equals isn't well documented; we use compareTo instead.
|
||||
if( NULL_UUID.compareTo(ticket) == 0 ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
OperationLog log = dehydratedLogs.get(ticket);
|
||||
dehydratedLogs.remove(ticket);
|
||||
return log;
|
||||
}
|
||||
logCache = new ParcelableCache<>();
|
||||
}
|
||||
|
||||
/** Holds the overall result, the number specifying varying degrees of success:
|
||||
@ -126,11 +84,8 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public OperationResult(Parcel source) {
|
||||
mResult = source.readInt();
|
||||
long mostSig = source.readLong();
|
||||
long leastSig = source.readLong();
|
||||
UUID mTicket = new UUID(mostSig, leastSig);
|
||||
// fetch the dehydrated log out of storage (this removes it from the dehydration pool)
|
||||
mLog = rehydrateLog(mTicket);
|
||||
// get log out of cache based on UUID from source
|
||||
mLog = logCache.readFromParcelAndGetFromCache(source);
|
||||
}
|
||||
|
||||
public int getResult() {
|
||||
@ -250,12 +205,20 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public Showable createNotify(final Activity activity) {
|
||||
|
||||
Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast());
|
||||
Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType);
|
||||
Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId());
|
||||
|
||||
// Take the last message as string
|
||||
int msgId = mLog.getLast().mType.getMsgId();
|
||||
String logText;
|
||||
|
||||
LogEntryParcel entryParcel = mLog.getLast();
|
||||
// special case: first parameter may be a quantity
|
||||
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
|
||||
&& entryParcel.mParameters[0] instanceof Integer) {
|
||||
logText = activity.getResources().getQuantityString(entryParcel.mType.getMsgId(),
|
||||
(Integer) entryParcel.mParameters[0],
|
||||
entryParcel.mParameters);
|
||||
} else {
|
||||
logText = activity.getString(entryParcel.mType.getMsgId(),
|
||||
entryParcel.mParameters);
|
||||
}
|
||||
|
||||
Style style;
|
||||
|
||||
@ -273,19 +236,19 @@ public abstract class OperationResult implements Parcelable {
|
||||
}
|
||||
|
||||
if (getLog() == null || getLog().isEmpty()) {
|
||||
return Notify.create(activity, msgId, Notify.LENGTH_LONG, style);
|
||||
return Notify.create(activity, logText, Notify.LENGTH_LONG, style);
|
||||
}
|
||||
|
||||
return Notify.create(activity, msgId, Notify.LENGTH_LONG, style,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
return Notify.create(activity, logText, Notify.LENGTH_LONG, style,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
|
||||
}
|
||||
|
||||
@ -512,6 +475,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (LogLevel.START, R.string.msg_mr),
|
||||
MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert),
|
||||
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
|
||||
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
|
||||
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint),
|
||||
@ -521,6 +485,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_MF_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify),
|
||||
MSG_MF_ERROR_NOEXIST_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_noexist_primary),
|
||||
MSG_MF_ERROR_NOEXIST_REVOKE (LogLevel.ERROR, R.string.msg_mf_error_noexist_revoke),
|
||||
MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop),
|
||||
MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry),
|
||||
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
|
||||
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
|
||||
@ -538,6 +503,9 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail),
|
||||
MSG_MF_PRIMARY_REPLACE_OLD (LogLevel.DEBUG, R.string.msg_mf_primary_replace_old),
|
||||
MSG_MF_PRIMARY_NEW (LogLevel.DEBUG, R.string.msg_mf_primary_new),
|
||||
MSG_MF_RESTRICTED_MODE (LogLevel.INFO, R.string.msg_mf_restricted_mode),
|
||||
MSG_MF_REQUIRE_DIVERT (LogLevel.OK, R.string.msg_mf_require_divert),
|
||||
MSG_MF_REQUIRE_PASSPHRASE (LogLevel.OK, R.string.msg_mf_require_passphrase),
|
||||
MSG_MF_SUBKEY_CHANGE (LogLevel.INFO, R.string.msg_mf_subkey_change),
|
||||
MSG_MF_SUBKEY_NEW_ID (LogLevel.DEBUG, R.string.msg_mf_subkey_new_id),
|
||||
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
|
||||
@ -590,13 +558,11 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
// promote key
|
||||
MSG_PR (LogLevel.START, R.string.msg_pr),
|
||||
MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret),
|
||||
MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found),
|
||||
MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
|
||||
MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
|
||||
|
||||
// messages used in UI code
|
||||
MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert),
|
||||
MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy),
|
||||
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found),
|
||||
|
||||
@ -660,7 +626,6 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_SE_ERROR_INPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_input_uri_not_found),
|
||||
MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_output_uri_not_found),
|
||||
MSG_SE_ERROR_TOO_MANY_INPUTS (LogLevel.ERROR, R.string.msg_se_error_too_many_inputs),
|
||||
MSG_SE_WARN_OUTPUT_LEFT (LogLevel.WARN, R.string.msg_se_warn_output_left),
|
||||
MSG_SE_SUCCESS (LogLevel.OK, R.string.msg_se_success),
|
||||
|
||||
// pgpsignencrypt
|
||||
@ -697,9 +662,9 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found),
|
||||
MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing),
|
||||
MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock),
|
||||
MSG_CRT_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_crt_error_divert),
|
||||
MSG_CRT (LogLevel.START, R.string.msg_crt),
|
||||
MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch),
|
||||
MSG_CRT_NFC_RETURN (LogLevel.OK, R.string.msg_crt_nfc_return),
|
||||
MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save),
|
||||
MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving),
|
||||
MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success),
|
||||
@ -823,11 +788,8 @@ public abstract class OperationResult implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mResult);
|
||||
// Get a ticket for our log.
|
||||
UUID mTicket = dehydrateLog(mLog);
|
||||
// And write out the UUID most and least significant bits.
|
||||
dest.writeLong(mTicket.getMostSignificantBits());
|
||||
dest.writeLong(mTicket.getLeastSignificantBits());
|
||||
// cache log and write UUID to dest
|
||||
logCache.cacheAndWriteToParcel(mLog, dest);
|
||||
}
|
||||
|
||||
public static class OperationLog implements Iterable<LogEntryParcel> {
|
||||
|
@ -22,8 +22,10 @@ import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
public class PgpEditKeyResult extends OperationResult {
|
||||
|
||||
public class PgpEditKeyResult extends InputPendingResult {
|
||||
|
||||
private transient UncachedKeyRing mRing;
|
||||
public final long mRingMasterKeyId;
|
||||
@ -35,6 +37,11 @@ public class PgpEditKeyResult extends OperationResult {
|
||||
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
|
||||
}
|
||||
|
||||
public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
mRingMasterKeyId = Constants.key.none;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getRing() {
|
||||
return mRing;
|
||||
}
|
||||
|
@ -19,85 +19,31 @@ package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class PgpSignEncryptResult extends OperationResult {
|
||||
public class PgpSignEncryptResult extends InputPendingResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
// fifth to sixth bit in addition indicate specific type of pending
|
||||
public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16;
|
||||
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32;
|
||||
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
long mNfcKeyId;
|
||||
byte[] mNfcHash;
|
||||
int mNfcAlgo;
|
||||
Date mNfcTimestamp;
|
||||
Passphrase mNfcPassphrase;
|
||||
byte[] mDetachedSignature;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
|
||||
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, Passphrase passphrase) {
|
||||
mNfcKeyId = nfcKeyId;
|
||||
mNfcHash = nfcHash;
|
||||
mNfcAlgo = nfcAlgo;
|
||||
mNfcTimestamp = nfcTimestamp;
|
||||
mNfcPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public void setDetachedSignature(byte[] detachedSignature) {
|
||||
mDetachedSignature = detachedSignature;
|
||||
}
|
||||
|
||||
public long getNfcKeyId() {
|
||||
return mNfcKeyId;
|
||||
}
|
||||
|
||||
public byte[] getNfcHash() {
|
||||
return mNfcHash;
|
||||
}
|
||||
|
||||
public int getNfcAlgo() {
|
||||
return mNfcAlgo;
|
||||
}
|
||||
|
||||
public Date getNfcTimestamp() {
|
||||
return mNfcTimestamp;
|
||||
}
|
||||
|
||||
public Passphrase getNfcPassphrase() {
|
||||
return mNfcPassphrase;
|
||||
}
|
||||
|
||||
public byte[] getDetachedSignature() {
|
||||
return mDetachedSignature;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||
super(log, requiredInput);
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(Parcel source) {
|
||||
super(source);
|
||||
mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
mNfcAlgo = source.readInt();
|
||||
mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||
mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
}
|
||||
|
||||
@ -107,19 +53,6 @@ public class PgpSignEncryptResult extends OperationResult {
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
if (mNfcHash != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mNfcHash);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mNfcAlgo);
|
||||
if (mNfcTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mDetachedSignature != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mDetachedSignature);
|
||||
@ -138,4 +71,4 @@ public class PgpSignEncryptResult extends OperationResult {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -31,13 +31,18 @@ public class PromoteKeyResult extends OperationResult {
|
||||
|
||||
public PromoteKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mMasterKeyId = source.readLong();
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
if (mMasterKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() {
|
||||
|
@ -21,20 +21,17 @@ import android.os.Parcel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SignEncryptResult extends OperationResult {
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
|
||||
public class SignEncryptResult extends InputPendingResult {
|
||||
|
||||
ArrayList<PgpSignEncryptResult> mResults;
|
||||
byte[] mResultBytes;
|
||||
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
public PgpSignEncryptResult getPending() {
|
||||
for (PgpSignEncryptResult sub : mResults) {
|
||||
if (sub.isPending()) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, ArrayList<PgpSignEncryptResult> results) {
|
||||
super(log, requiredInput);
|
||||
mResults = results;
|
||||
}
|
||||
|
||||
public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results) {
|
||||
|
@ -98,11 +98,14 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
/** Create a dummy secret ring from this key */
|
||||
public UncachedKeyRing createDummySecretRing () {
|
||||
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
|
||||
S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
|
||||
return new UncachedKeyRing(secRing);
|
||||
}
|
||||
|
||||
/** Create a dummy secret ring from this key */
|
||||
public UncachedKeyRing createDivertSecretRing (byte[] cardAid) {
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
|
||||
return new UncachedKeyRing(secRing);
|
||||
}
|
||||
|
||||
}
|
@ -40,13 +40,17 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for a PGPSecretKey.
|
||||
@ -184,13 +188,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
return PgpConstants.sPreferredHashAlgorithms;
|
||||
}
|
||||
|
||||
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash,
|
||||
Date nfcCreationTimestamp) {
|
||||
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
|
||||
Map<ByteBuffer,byte[]> signedHashes) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
||||
// use synchronous "NFC based" SignerBuilder
|
||||
return new NfcSyncPGPContentSignerBuilder(
|
||||
mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
|
||||
mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp)
|
||||
mSecretKey.getKeyID(), signedHashes)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
} else {
|
||||
// content signer based on signing key algorithm and chosen hash algorithm
|
||||
@ -200,29 +204,43 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext,
|
||||
byte[] nfcSignedHash, Date nfcCreationTimestamp)
|
||||
throws PgpGeneralException {
|
||||
public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
|
||||
PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
|
||||
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
if (nfcSignedHash != null && nfcCreationTimestamp == null) {
|
||||
throw new PgpGeneralException("Got nfc hash without timestamp!!");
|
||||
|
||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
try {
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
|
||||
return signatureGenerator;
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public PGPSignatureGenerator getDataSignatureGenerator(int hashAlgo, boolean cleartext,
|
||||
Map<ByteBuffer, byte[]> signedHashes, Date creationTimestamp)
|
||||
throws PgpGeneralException {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
|
||||
// We explicitly create a signature creation timestamp in this place.
|
||||
// That way, we can inject an artificial one from outside, ie the one
|
||||
// used in previous runs of this function.
|
||||
if (nfcCreationTimestamp == null) {
|
||||
if (creationTimestamp == null) {
|
||||
// to sign using nfc PgpSignEncrypt is executed two times.
|
||||
// the first time it stops to return the PendingIntent for nfc connection and signing the hash
|
||||
// the second time the signed hash is used.
|
||||
// to get the same hash we cache the timestamp for the second round!
|
||||
nfcCreationTimestamp = new Date();
|
||||
creationTimestamp = new Date();
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo,
|
||||
nfcSignedHash, nfcCreationTimestamp);
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, signedHashes);
|
||||
|
||||
int signatureType;
|
||||
if (cleartext) {
|
||||
@ -238,7 +256,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
spGen.setSignatureCreationTime(false, creationTimestamp);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
return signatureGenerator;
|
||||
} catch (PgpKeyNotFoundException | PGPException e) {
|
||||
@ -247,145 +265,24 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
public PublicKeyDataDecryptorFactory getDecryptorFactory(byte[] nfcDecryptedSessionKey) {
|
||||
public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
||||
return new NfcSyncPublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(nfcDecryptedSessionKey);
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
cryptoInput.getCryptoData()
|
||||
);
|
||||
} else {
|
||||
return new JcePublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Certify the given pubkeyid with the given masterkeyid.
|
||||
*
|
||||
* @param publicKeyRing Keyring to add certification to.
|
||||
* @param userIds User IDs to certify
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds,
|
||||
byte[] nfcSignedHash, Date nfcCreationTimestamp) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
if (!isMasterKey()) {
|
||||
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
|
||||
}
|
||||
if (publicKeyRing.getMasterKeyId() == getKeyId()) {
|
||||
throw new AssertionError("key tried to self-certify, this is a programming error!");
|
||||
}
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator;
|
||||
{
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
|
||||
PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
|
||||
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
try {
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
if (nfcCreationTimestamp != null) {
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
|
||||
}
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
try {
|
||||
for (String userId : userIds) {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
|
||||
|
||||
return new UncachedKeyRing(ring);
|
||||
}
|
||||
|
||||
/**
|
||||
* Certify the given user attributes with the given masterkeyid.
|
||||
*
|
||||
* @param publicKeyRing Keyring to add certification to.
|
||||
* @param userAttributes User IDs to certify, or all if null
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing,
|
||||
List<WrappedUserAttribute> userAttributes, byte[] nfcSignedHash, Date nfcCreationTimestamp) {
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||
throw new PrivateKeyNotUnlockedException();
|
||||
}
|
||||
if (!isMasterKey()) {
|
||||
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
|
||||
}
|
||||
if (publicKeyRing.getMasterKeyId() == getKeyId()) {
|
||||
throw new AssertionError("key tried to self-certify, this is a programming error!");
|
||||
}
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator;
|
||||
{
|
||||
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
|
||||
PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
|
||||
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
try {
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
if (nfcCreationTimestamp != null) {
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
|
||||
}
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
try {
|
||||
for (WrappedUserAttribute userAttribute : userAttributes) {
|
||||
PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
|
||||
PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
|
||||
|
||||
return new UncachedKeyRing(ring);
|
||||
public byte[] getIv() {
|
||||
return mSecretKey.getIV();
|
||||
}
|
||||
|
||||
static class PrivateKeyNotUnlockedException extends RuntimeException {
|
||||
@ -402,4 +299,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
return mPrivateKey;
|
||||
}
|
||||
|
||||
// HACK, for TESTING ONLY!!
|
||||
PGPSecretKey getSecretKey() {
|
||||
return mSecretKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,149 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
public class PgpCertifyOperation {
|
||||
|
||||
public PgpCertifyResult certify(
|
||||
CanonicalizedSecretKey secretKey,
|
||||
CanonicalizedPublicKeyRing publicRing,
|
||||
OperationLog log,
|
||||
int indent,
|
||||
CertifyAction action,
|
||||
Map<ByteBuffer,byte[]> signedHashes,
|
||||
Date creationTimestamp) {
|
||||
|
||||
if (!secretKey.isMasterKey()) {
|
||||
throw new AssertionError("tried to certify with non-master key, this is a programming error!");
|
||||
}
|
||||
if (publicRing.getMasterKeyId() == secretKey.getKeyId()) {
|
||||
throw new AssertionError("key tried to self-certify, this is a programming error!");
|
||||
}
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator = secretKey.getCertSignatureGenerator(signedHashes);
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
if (creationTimestamp != null) {
|
||||
spGen.setSignatureCreationTime(false, creationTimestamp);
|
||||
Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
|
||||
}
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// get the master subkey (which we certify for)
|
||||
PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
|
||||
|
||||
NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp,
|
||||
publicKey.getKeyID(), publicKey.getKeyID());
|
||||
|
||||
try {
|
||||
if (action.mUserIds != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (String userId : action.mUserIds) {
|
||||
try {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
requiredInput.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (action.mUserAttributes != null) {
|
||||
log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
|
||||
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (WrappedUserAttribute userAttribute : action.mUserAttributes) {
|
||||
PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
|
||||
try {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
requiredInput.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Log.e(Constants.TAG, "signing error", e);
|
||||
return new PgpCertifyResult();
|
||||
}
|
||||
|
||||
if (!requiredInput.isEmpty()) {
|
||||
return new PgpCertifyResult(requiredInput.build());
|
||||
}
|
||||
|
||||
PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicRing.getRing(), publicKey);
|
||||
return new PgpCertifyResult(new UncachedKeyRing(ring));
|
||||
|
||||
}
|
||||
|
||||
public static class PgpCertifyResult {
|
||||
|
||||
final RequiredInputParcel mRequiredInput;
|
||||
final UncachedKeyRing mCertifiedRing;
|
||||
|
||||
PgpCertifyResult() {
|
||||
mRequiredInput = null;
|
||||
mCertifiedRing = null;
|
||||
}
|
||||
|
||||
PgpCertifyResult(RequiredInputParcel requiredInput) {
|
||||
mRequiredInput = requiredInput;
|
||||
mCertifiedRing = null;
|
||||
}
|
||||
|
||||
PgpCertifyResult(UncachedKeyRing certifiedRing) {
|
||||
mRequiredInput = null;
|
||||
mCertifiedRing = certifiedRing;
|
||||
}
|
||||
|
||||
public boolean success() {
|
||||
return mCertifiedRing != null || mRequiredInput != null;
|
||||
}
|
||||
|
||||
public boolean nfcInputRequired() {
|
||||
return mRequiredInput != null;
|
||||
}
|
||||
|
||||
public UncachedKeyRing getCertifiedRing() {
|
||||
return mCertifiedRing;
|
||||
}
|
||||
|
||||
public RequiredInputParcel getRequiredInput() {
|
||||
return mRequiredInput;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -47,16 +47,15 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -84,10 +83,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private OutputStream mOutStream;
|
||||
|
||||
private boolean mAllowSymmetricDecryption;
|
||||
private Passphrase mPassphrase;
|
||||
private Set<Long> mAllowedKeyIds;
|
||||
private boolean mDecryptMetadataOnly;
|
||||
private byte[] mDecryptedSessionKey;
|
||||
private byte[] mDetachedSignature;
|
||||
private String mRequiredSignerFingerprint;
|
||||
private boolean mSignedLiteralData;
|
||||
@ -100,10 +97,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
this.mOutStream = builder.mOutStream;
|
||||
|
||||
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
|
||||
this.mPassphrase = builder.mPassphrase;
|
||||
this.mAllowedKeyIds = builder.mAllowedKeyIds;
|
||||
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
||||
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
|
||||
this.mDetachedSignature = builder.mDetachedSignature;
|
||||
this.mSignedLiteralData = builder.mSignedLiteralData;
|
||||
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
|
||||
@ -119,10 +114,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private OutputStream mOutStream = null;
|
||||
private Progressable mProgressable = null;
|
||||
private boolean mAllowSymmetricDecryption = true;
|
||||
private Passphrase mPassphrase = null;
|
||||
private Set<Long> mAllowedKeyIds = null;
|
||||
private boolean mDecryptMetadataOnly = false;
|
||||
private byte[] mDecryptedSessionKey = null;
|
||||
private byte[] mDetachedSignature = null;
|
||||
private String mRequiredSignerFingerprint = null;
|
||||
private boolean mSignedLiteralData = false;
|
||||
@ -160,11 +153,6 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPassphrase(Passphrase passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow these key ids alone for decryption.
|
||||
* This means only ciphertexts encrypted for one of these private key can be decrypted.
|
||||
@ -183,11 +171,6 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNfcState(byte[] decryptedSessionKey) {
|
||||
mDecryptedSessionKey = decryptedSessionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If detachedSignature != null, it will be used exclusively to verify the signature
|
||||
*/
|
||||
@ -204,7 +187,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
/**
|
||||
* Decrypts and/or verifies data based on parameters of class
|
||||
*/
|
||||
public DecryptVerifyResult execute() {
|
||||
public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
|
||||
try {
|
||||
if (mDetachedSignature != null) {
|
||||
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
|
||||
@ -226,10 +209,10 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
} else {
|
||||
// else: ascii armored encryption! go on...
|
||||
return decryptVerify(in, 0);
|
||||
return decryptVerify(cryptoInput, in, 0);
|
||||
}
|
||||
} else {
|
||||
return decryptVerify(in, 0);
|
||||
return decryptVerify(cryptoInput, in, 0);
|
||||
}
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
@ -248,7 +231,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
/**
|
||||
* Verify Keybase.io style signed literal data
|
||||
*/
|
||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException {
|
||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
|
||||
throws IOException, PGPException {
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_VL, indent);
|
||||
|
||||
@ -378,7 +362,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
/**
|
||||
* Decrypt and/or verifies binary or ascii armored pgp
|
||||
*/
|
||||
private DecryptVerifyResult decryptVerify(InputStream in, int indent) throws IOException, PGPException {
|
||||
private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
|
||||
InputStream in, int indent) throws IOException, PGPException {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
@ -433,6 +418,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
Passphrase passphrase = null;
|
||||
|
||||
// go through all objects and find one we can decrypt
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
@ -492,11 +479,15 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
encryptedDataAsymmetric = encData;
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (mPassphrase == null) {
|
||||
if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
|
||||
passphrase = null;
|
||||
} else if (cryptoInput.hasPassphrase()) {
|
||||
passphrase = cryptoInput.getPassphrase();
|
||||
} else {
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
try {
|
||||
// returns "" if key has no passphrase
|
||||
mPassphrase = getCachedPassphrase(subKeyId);
|
||||
passphrase = getCachedPassphrase(subKeyId);
|
||||
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||
@ -504,12 +495,11 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
if (passphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(subKeyId);
|
||||
return result;
|
||||
return new DecryptVerifyResult(log,
|
||||
RequiredInputParcel.createRequiredDecryptPassphrase(
|
||||
secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,11 +526,14 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
// if no passphrase is given, return here
|
||||
// indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
if (!cryptoInput.hasPassphrase()) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE, log);
|
||||
return new DecryptVerifyResult(log,
|
||||
RequiredInputParcel.createRequiredSymmetricPassphrase());
|
||||
}
|
||||
|
||||
passphrase = cryptoInput.getPassphrase();
|
||||
|
||||
// break out of while, only decrypt the first packet
|
||||
break;
|
||||
}
|
||||
@ -573,7 +566,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
|
||||
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
|
||||
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
mPassphrase.getCharArray());
|
||||
passphrase.getCharArray());
|
||||
|
||||
clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
||||
encryptedData = encryptedDataSymmetric;
|
||||
@ -585,7 +578,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
||||
if (!secretEncryptionKey.unlock(mPassphrase)) {
|
||||
if (!secretEncryptionKey.unlock(passphrase)) {
|
||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
@ -599,16 +592,15 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
try {
|
||||
PublicKeyDataDecryptorFactory decryptorFactory
|
||||
= secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey);
|
||||
= secretEncryptionKey.getDecryptorFactory(cryptoInput);
|
||||
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||
|
||||
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
||||
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
|
||||
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log);
|
||||
result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase);
|
||||
return result;
|
||||
return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
|
||||
e.encryptedSessionKey, secretEncryptionKey.getKeyId()
|
||||
));
|
||||
}
|
||||
encryptedData = encryptedDataAsymmetric;
|
||||
} else {
|
||||
@ -878,8 +870,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
* The method is heavily based on
|
||||
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
||||
*/
|
||||
private DecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn, int indent)
|
||||
throws IOException, PGPException {
|
||||
private DecryptVerifyResult verifyCleartextSignature(
|
||||
ArmoredInputStream aIn, int indent) throws IOException, PGPException {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.bcpg.sig.Features;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
||||
@ -43,6 +43,8 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBu
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
@ -54,6 +56,9 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -86,6 +91,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* This indicator may be null.
|
||||
*/
|
||||
public class PgpKeyOperation {
|
||||
|
||||
private Stack<Progressable> mProgress;
|
||||
private AtomicBoolean mCancelled;
|
||||
|
||||
@ -317,7 +323,8 @@ public class PgpKeyOperation {
|
||||
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
|
||||
|
||||
subProgressPush(50, 100);
|
||||
return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, new Passphrase(), log);
|
||||
CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase(""));
|
||||
return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log);
|
||||
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
@ -348,8 +355,9 @@ public class PgpKeyOperation {
|
||||
* namely stripping of subkeys and changing the protection mode of dummy keys.
|
||||
*
|
||||
*/
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
Passphrase passphrase) {
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
||||
CryptoInputParcel cryptoInput,
|
||||
SaveKeyringParcel saveParcel) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
@ -387,11 +395,24 @@ public class PgpKeyOperation {
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// If we have no passphrase, only allow restricted operation
|
||||
if (passphrase == null) {
|
||||
if (saveParcel.isEmpty()) {
|
||||
log.add(LogType.MSG_MF_ERROR_NOOP, indent);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
|
||||
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
|
||||
return internalRestricted(sKR, saveParcel, log);
|
||||
}
|
||||
|
||||
// Do we require a passphrase? If so, pass it along
|
||||
if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) {
|
||||
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
|
||||
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
|
||||
cryptoInput.getSignatureTime()));
|
||||
}
|
||||
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
@ -399,33 +420,45 @@ public class PgpKeyOperation {
|
||||
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
|
||||
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log);
|
||||
|
||||
}
|
||||
|
||||
private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
int masterKeyFlags, long masterKeyExpiry,
|
||||
SaveKeyringParcel saveParcel, Passphrase passphrase,
|
||||
CryptoInputParcel cryptoInput,
|
||||
SaveKeyringParcel saveParcel,
|
||||
OperationLog log) {
|
||||
|
||||
int indent = 1;
|
||||
|
||||
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
|
||||
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
|
||||
masterSecretKey.getKeyID());
|
||||
|
||||
progress(R.string.progress_modify, 0);
|
||||
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
|
||||
// 1. Unlock private key
|
||||
progress(R.string.progress_modify_unlock, 10);
|
||||
log.add(LogType.MSG_MF_UNLOCK, indent);
|
||||
PGPPrivateKey masterPrivateKey;
|
||||
{
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
|
||||
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
|
||||
if (isDivertToCard(masterSecretKey)) {
|
||||
masterPrivateKey = null;
|
||||
log.add(LogType.MSG_MF_DIVERT, indent);
|
||||
} else {
|
||||
|
||||
// 1. Unlock private key
|
||||
progress(R.string.progress_modify_unlock, 10);
|
||||
log.add(LogType.MSG_MF_UNLOCK, indent);
|
||||
{
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(cryptoInput.getPassphrase().getCharArray());
|
||||
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +482,7 @@ public class PgpKeyOperation {
|
||||
String userId = saveParcel.mAddUserIds.get(i);
|
||||
log.add(LogType.MSG_MF_UID_ADD, indent, userId);
|
||||
|
||||
if (userId.equals("")) {
|
||||
if ("".equals(userId)) {
|
||||
log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
@ -480,9 +513,16 @@ public class PgpKeyOperation {
|
||||
boolean isPrimary = saveParcel.mChangePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.mChangePrimaryUserId);
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
|
||||
masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
try {
|
||||
PGPSignature cert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId,
|
||||
isPrimary, masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@ -509,9 +549,15 @@ public class PgpKeyOperation {
|
||||
PGPUserAttributeSubpacketVector vector = attribute.getVector();
|
||||
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
|
||||
masterPublicKey, vector);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
|
||||
try {
|
||||
PGPSignature cert = generateUserAttributeSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, vector);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@ -539,9 +585,15 @@ public class PgpKeyOperation {
|
||||
|
||||
// a duplicate revocation will be removed during canonicalization, so no need to
|
||||
// take care of that here.
|
||||
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
|
||||
masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
try {
|
||||
PGPSignature cert = generateRevocationSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@ -611,11 +663,18 @@ public class PgpKeyOperation {
|
||||
log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, false,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
try {
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId, false,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -627,11 +686,17 @@ public class PgpKeyOperation {
|
||||
log.add(LogType.MSG_MF_PRIMARY_NEW, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, true,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
try {
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId, true,
|
||||
masterKeyFlags, masterKeyExpiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
ok = true;
|
||||
}
|
||||
|
||||
@ -718,8 +783,9 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
PGPPublicKey pKey =
|
||||
updateMasterCertificates(masterPrivateKey, masterPublicKey,
|
||||
flags, expiry, indent, log);
|
||||
updateMasterCertificates(
|
||||
masterSecretKey, masterPrivateKey, masterPublicKey,
|
||||
flags, expiry, cryptoInput, nfcSignOps, indent, log);
|
||||
if (pKey == null) {
|
||||
// error log entry has already been added by updateMasterCertificates itself
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
@ -756,9 +822,16 @@ public class PgpKeyOperation {
|
||||
pKey = PGPPublicKey.removeCertification(pKey, sig);
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
cryptoInput.getPassphrase().getCharArray());
|
||||
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
|
||||
PGPSignature sig = generateSubkeyBindingSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry);
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
|
||||
sKey, pKey, flags, expiry, passphrase);
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
@ -782,10 +855,17 @@ public class PgpKeyOperation {
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey);
|
||||
try {
|
||||
PGPSignature sig = generateRevocationSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterPrivateKey, pKey);
|
||||
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
@ -828,10 +908,16 @@ public class PgpKeyOperation {
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
try {
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
@ -840,7 +926,8 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
cryptoInput.getPassphrase().getCharArray());
|
||||
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
|
||||
@ -868,7 +955,7 @@ public class PgpKeyOperation {
|
||||
indent += 1;
|
||||
|
||||
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
|
||||
passphrase, saveParcel.mNewUnlock, log, indent);
|
||||
cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent);
|
||||
if (sKR == null) {
|
||||
// The error has been logged above, just return a bad state
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
@ -892,6 +979,12 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
progress(R.string.progress_done, 100);
|
||||
|
||||
if (!nfcSignOps.isEmpty()) {
|
||||
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
|
||||
return new PgpEditKeyResult(log, nfcSignOps.build());
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_MF_SUCCESS, indent);
|
||||
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||
|
||||
@ -1064,8 +1157,7 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
|
||||
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
newPassphrase.getCharArray());
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
|
||||
|
||||
// noinspection unchecked
|
||||
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
|
||||
@ -1116,9 +1208,13 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
/** Update all (non-revoked) uid signatures with new flags and expiry time. */
|
||||
private static PGPPublicKey updateMasterCertificates(
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey,
|
||||
int flags, long expiry, int indent, OperationLog log)
|
||||
private PGPPublicKey updateMasterCertificates(
|
||||
PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPublicKey masterPublicKey,
|
||||
int flags, long expiry,
|
||||
CryptoInputParcel cryptoInput,
|
||||
NfcSignOperationsBuilder nfcSignOps,
|
||||
int indent, OperationLog log)
|
||||
throws PGPException, IOException, SignatureException {
|
||||
|
||||
// keep track if we actually changed one
|
||||
@ -1173,10 +1269,16 @@ public class PgpKeyOperation {
|
||||
currentCert.getHashedSubPackets().isPrimaryUserID();
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
try {
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
getSignatureGenerator(masterSecretKey, cryptoInput),
|
||||
cryptoInput.getSignatureTime(),
|
||||
masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
} catch (NfcInteractionNeeded e) {
|
||||
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
|
||||
}
|
||||
ok = true;
|
||||
|
||||
}
|
||||
@ -1191,15 +1293,37 @@ public class PgpKeyOperation {
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserIdSignature(
|
||||
static PGPSignatureGenerator getSignatureGenerator(
|
||||
PGPSecretKey secretKey, CryptoInputParcel cryptoInput) {
|
||||
|
||||
PGPContentSignerBuilder builder;
|
||||
|
||||
S2K s2k = secretKey.getS2K();
|
||||
if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
|
||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
|
||||
// use synchronous "NFC based" SignerBuilder
|
||||
builder = new NfcSyncPGPContentSignerBuilder(
|
||||
secretKey.getPublicKey().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO,
|
||||
secretKey.getKeyID(), cryptoInput.getCryptoData())
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
} else {
|
||||
// content signer based on signing key algorithm and chosen hash algorithm
|
||||
builder = new JcaPGPContentSignerBuilder(
|
||||
secretKey.getPublicKey().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
}
|
||||
|
||||
return new PGPSignatureGenerator(builder);
|
||||
|
||||
}
|
||||
|
||||
private PGPSignature generateUserIdSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
|
||||
int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
{
|
||||
@ -1223,7 +1347,7 @@ public class PgpKeyOperation {
|
||||
hashedPacketsGen.setPrimaryUserID(false, primary);
|
||||
|
||||
/* critical subpackets: we consider those important for a modern pgp implementation */
|
||||
hashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
hashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
// Request that senders add the MDC to the message (authenticate unsigned messages)
|
||||
hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
|
||||
hashedPacketsGen.setKeyFlags(true, flags);
|
||||
@ -1239,19 +1363,15 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserAttributeSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
|
||||
PGPUserAttributeSubpacketVector vector)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
{
|
||||
/* critical subpackets: we consider those important for a modern pgp implementation */
|
||||
hashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
hashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
}
|
||||
|
||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||
@ -1260,29 +1380,24 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
private static PGPSignature generateRevocationSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
|
||||
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||
PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
|
||||
return sGen.generateCertification(userId, pKey);
|
||||
}
|
||||
|
||||
private static PGPSignature generateRevocationSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, new Date());
|
||||
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
// Generate key revocation or subkey revocation, depending on master/subkey-ness
|
||||
if (masterPublicKey.getKeyID() == pKey.getKeyID()) {
|
||||
@ -1294,26 +1409,12 @@ public class PgpKeyOperation {
|
||||
}
|
||||
}
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, Passphrase passphrase)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
passphrase.getCharArray());
|
||||
PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
|
||||
return generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, subPrivateKey,
|
||||
pKey, flags, expiry);
|
||||
}
|
||||
|
||||
static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPSignatureGenerator sGen, Date creationTime,
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
|
||||
// date for signing
|
||||
Date creationTime = new Date();
|
||||
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
// If this key can sign, we need a primary key binding signature
|
||||
@ -1324,10 +1425,10 @@ public class PgpKeyOperation {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey);
|
||||
PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder);
|
||||
subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
|
||||
subSigGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey);
|
||||
unhashedPacketsGen.setEmbeddedSignature(true, certification);
|
||||
}
|
||||
|
||||
@ -1342,10 +1443,6 @@ public class PgpKeyOperation {
|
||||
}
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey);
|
||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
|
||||
@ -1372,4 +1469,16 @@ public class PgpKeyOperation {
|
||||
return flags;
|
||||
}
|
||||
|
||||
private static boolean isDummy(PGPSecretKey secretKey) {
|
||||
S2K s2k = secretKey.getS2K();
|
||||
return s2k.getType() == S2K.GNU_DUMMY_S2K
|
||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY;
|
||||
}
|
||||
|
||||
private static boolean isDivertToCard(PGPSecretKey secretKey) {
|
||||
S2K s2k = secretKey.getS2K();
|
||||
return s2k.getType() == S2K.GNU_DUMMY_S2K
|
||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,11 +20,18 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public class PgpSignEncryptInput {
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
|
||||
public class PgpSignEncryptInputParcel implements Parcelable {
|
||||
|
||||
protected String mVersionHeader = null;
|
||||
protected boolean mEnableAsciiArmorOutput = false;
|
||||
@ -35,16 +42,68 @@ public class PgpSignEncryptInput {
|
||||
protected long mSignatureMasterKeyId = Constants.key.none;
|
||||
protected Long mSignatureSubKeyId = null;
|
||||
protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
|
||||
protected Passphrase mSignaturePassphrase = null;
|
||||
protected long mAdditionalEncryptId = Constants.key.none;
|
||||
protected byte[] mNfcSignedHash = null;
|
||||
protected Date mNfcCreationTimestamp = null;
|
||||
protected boolean mFailOnMissingEncryptionKeyIds = false;
|
||||
protected String mCharset;
|
||||
protected boolean mCleartextSignature;
|
||||
protected boolean mDetachedSignature = false;
|
||||
protected boolean mHiddenRecipients = false;
|
||||
|
||||
public PgpSignEncryptInputParcel() {
|
||||
|
||||
}
|
||||
|
||||
PgpSignEncryptInputParcel(Parcel source) {
|
||||
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
|
||||
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||
mVersionHeader = source.readString();
|
||||
mEnableAsciiArmorOutput = source.readInt() == 1;
|
||||
mCompressionId = source.readInt();
|
||||
mEncryptionMasterKeyIds = source.createLongArray();
|
||||
mSymmetricPassphrase = source.readParcelable(loader);
|
||||
mSymmetricEncryptionAlgorithm = source.readInt();
|
||||
mSignatureMasterKeyId = source.readLong();
|
||||
mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null;
|
||||
mSignatureHashAlgorithm = source.readInt();
|
||||
mAdditionalEncryptId = source.readLong();
|
||||
mFailOnMissingEncryptionKeyIds = source.readInt() == 1;
|
||||
mCharset = source.readString();
|
||||
mCleartextSignature = source.readInt() == 1;
|
||||
mDetachedSignature = source.readInt() == 1;
|
||||
mHiddenRecipients = source.readInt() == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mVersionHeader);
|
||||
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
|
||||
dest.writeInt(mCompressionId);
|
||||
dest.writeLongArray(mEncryptionMasterKeyIds);
|
||||
dest.writeParcelable(mSymmetricPassphrase, 0);
|
||||
dest.writeInt(mSymmetricEncryptionAlgorithm);
|
||||
dest.writeLong(mSignatureMasterKeyId);
|
||||
if (mSignatureSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mSignatureHashAlgorithm);
|
||||
dest.writeLong(mAdditionalEncryptId);
|
||||
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
|
||||
dest.writeString(mCharset);
|
||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||
dest.writeInt(mHiddenRecipients ? 1 : 0);
|
||||
}
|
||||
|
||||
public String getCharset() {
|
||||
return mCharset;
|
||||
}
|
||||
@ -57,37 +116,20 @@ public class PgpSignEncryptInput {
|
||||
return mFailOnMissingEncryptionKeyIds;
|
||||
}
|
||||
|
||||
public Date getNfcCreationTimestamp() {
|
||||
return mNfcCreationTimestamp;
|
||||
}
|
||||
|
||||
public byte[] getNfcSignedHash() {
|
||||
return mNfcSignedHash;
|
||||
}
|
||||
|
||||
public long getAdditionalEncryptId() {
|
||||
return mAdditionalEncryptId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) {
|
||||
public PgpSignEncryptInputParcel setAdditionalEncryptId(long additionalEncryptId) {
|
||||
mAdditionalEncryptId = additionalEncryptId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Passphrase getSignaturePassphrase() {
|
||||
return mSignaturePassphrase;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignaturePassphrase(Passphrase signaturePassphrase) {
|
||||
mSignaturePassphrase = signaturePassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSignatureHashAlgorithm() {
|
||||
return mSignatureHashAlgorithm;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
mSignatureHashAlgorithm = signatureHashAlgorithm;
|
||||
return this;
|
||||
}
|
||||
@ -96,7 +138,7 @@ public class PgpSignEncryptInput {
|
||||
return mSignatureSubKeyId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) {
|
||||
public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) {
|
||||
mSignatureSubKeyId = signatureSubKeyId;
|
||||
return this;
|
||||
}
|
||||
@ -105,7 +147,7 @@ public class PgpSignEncryptInput {
|
||||
return mSignatureMasterKeyId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
mSignatureMasterKeyId = signatureMasterKeyId;
|
||||
return this;
|
||||
}
|
||||
@ -114,7 +156,7 @@ public class PgpSignEncryptInput {
|
||||
return mSymmetricEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
|
||||
return this;
|
||||
}
|
||||
@ -123,7 +165,7 @@ public class PgpSignEncryptInput {
|
||||
return mSymmetricPassphrase;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSymmetricPassphrase(Passphrase symmetricPassphrase) {
|
||||
public PgpSignEncryptInputParcel setSymmetricPassphrase(Passphrase symmetricPassphrase) {
|
||||
mSymmetricPassphrase = symmetricPassphrase;
|
||||
return this;
|
||||
}
|
||||
@ -132,7 +174,7 @@ public class PgpSignEncryptInput {
|
||||
return mEncryptionMasterKeyIds;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
|
||||
public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
|
||||
mEncryptionMasterKeyIds = encryptionMasterKeyIds;
|
||||
return this;
|
||||
}
|
||||
@ -141,7 +183,7 @@ public class PgpSignEncryptInput {
|
||||
return mCompressionId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setCompressionId(int compressionId) {
|
||||
public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
|
||||
mCompressionId = compressionId;
|
||||
return this;
|
||||
}
|
||||
@ -154,28 +196,22 @@ public class PgpSignEncryptInput {
|
||||
return mVersionHeader;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setVersionHeader(String versionHeader) {
|
||||
public PgpSignEncryptInputParcel setVersionHeader(String versionHeader) {
|
||||
mVersionHeader = versionHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
public PgpSignEncryptInputParcel setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
mEnableAsciiArmorOutput = enableAsciiArmorOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
||||
public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
||||
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) {
|
||||
mNfcSignedHash = signedHash;
|
||||
mNfcCreationTimestamp = creationTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) {
|
||||
public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
|
||||
this.mCleartextSignature = cleartextSignature;
|
||||
return this;
|
||||
}
|
||||
@ -184,7 +220,7 @@ public class PgpSignEncryptInput {
|
||||
return mCleartextSignature;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) {
|
||||
public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) {
|
||||
this.mDetachedSignature = detachedSignature;
|
||||
return this;
|
||||
}
|
||||
@ -193,7 +229,7 @@ public class PgpSignEncryptInput {
|
||||
return mDetachedSignature;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) {
|
||||
public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) {
|
||||
this.mHiddenRecipients = hiddenRecipients;
|
||||
return this;
|
||||
}
|
||||
@ -201,5 +237,16 @@ public class PgpSignEncryptInput {
|
||||
public boolean isHiddenRecipients() {
|
||||
return mHiddenRecipients;
|
||||
}
|
||||
|
||||
public static final Creator<PgpSignEncryptInputParcel> CREATOR = new Creator<PgpSignEncryptInputParcel>() {
|
||||
public PgpSignEncryptInputParcel createFromParcel(final Parcel source) {
|
||||
return new PgpSignEncryptInputParcel(source);
|
||||
}
|
||||
|
||||
public PgpSignEncryptInputParcel[] newArray(final int size) {
|
||||
return new PgpSignEncryptInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
@ -44,11 +43,15 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -72,7 +75,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* <p/>
|
||||
* For a high-level operation based on URIs, see SignEncryptOperation.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput
|
||||
* @see PgpSignEncryptInputParcel
|
||||
* @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult
|
||||
* @see org.sufficientlysecure.keychain.operations.SignEncryptOperation
|
||||
*/
|
||||
@ -99,8 +102,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
/**
|
||||
* Signs and/or encrypts data based on parameters of class
|
||||
*/
|
||||
public PgpSignEncryptResult execute(PgpSignEncryptInput input,
|
||||
InputData inputData, OutputStream outputStream) {
|
||||
public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput,
|
||||
InputData inputData, OutputStream outputStream) {
|
||||
|
||||
int indent = 0;
|
||||
OperationLog log = new OperationLog();
|
||||
@ -128,7 +131,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
ArmoredOutputStream armorOut = null;
|
||||
OutputStream out;
|
||||
if (input.isEnableAsciiArmorOutput()) {
|
||||
armorOut = new ArmoredOutputStream(outputStream);
|
||||
armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
|
||||
if (input.getVersionHeader() != null) {
|
||||
armorOut.setHeader("Version", input.getVersionHeader());
|
||||
}
|
||||
@ -145,62 +148,62 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
CanonicalizedSecretKey signingKey = null;
|
||||
if (enableSignature) {
|
||||
|
||||
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
|
||||
|
||||
try {
|
||||
// fetch the indicated master key id (the one whose name we sign in)
|
||||
CanonicalizedSecretKeyRing signingKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
|
||||
|
||||
long signKeyId;
|
||||
// use specified signing subkey, or find the one to use
|
||||
if (input.getSignatureSubKeyId() == null) {
|
||||
signKeyId = signingKeyRing.getSecretSignId();
|
||||
} else {
|
||||
signKeyId = input.getSignatureSubKeyId();
|
||||
// fetch the specific subkey to sign with, or just use the master key if none specified
|
||||
signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
|
||||
|
||||
// Make sure we are allowed to sign here!
|
||||
if (!signingKey.canSign()) {
|
||||
log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// fetch the specific subkey to sign with, or just use the master key if none specified
|
||||
signingKey = signingKeyRing.getSecretKey(signKeyId);
|
||||
switch (signingKey.getSecretKeyType()) {
|
||||
case DIVERT_TO_CARD:
|
||||
case PASSPHRASE_EMPTY: {
|
||||
if (!signingKey.unlock(new Passphrase())) {
|
||||
throw new AssertionError(
|
||||
"PASSPHRASE_EMPTY/DIVERT_TO_CARD keyphrase not unlocked with empty passphrase."
|
||||
+ " This is a programming error!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException | PgpGeneralException e) {
|
||||
case PIN:
|
||||
case PATTERN:
|
||||
case PASSPHRASE: {
|
||||
if (cryptoInput.getPassphrase() == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
|
||||
cryptoInput.getSignatureTime()));
|
||||
}
|
||||
if (!signingKey.unlock(cryptoInput.getPassphrase())) {
|
||||
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GNU_DUMMY: {
|
||||
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError("Unhandled SecretKeyType! (should not happen)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to sign here!
|
||||
if (!signingKey.canSign()) {
|
||||
log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (input.getSignaturePassphrase() == null) {
|
||||
try {
|
||||
// returns "" if key has no passphrase
|
||||
input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId()));
|
||||
// TODO
|
||||
// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||
// TODO
|
||||
// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (input.getSignaturePassphrase() == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(signingKey.getKeyId());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
|
||||
|
||||
try {
|
||||
if (!signingKey.unlock(input.getSignaturePassphrase())) {
|
||||
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
@ -281,8 +284,9 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
|
||||
try {
|
||||
boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
|
||||
signatureGenerator = signingKey.getSignatureGenerator(
|
||||
input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp());
|
||||
signatureGenerator = signingKey.getDataSignatureGenerator(
|
||||
input.getSignatureHashAlgorithm(), cleartext,
|
||||
cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_NFC, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
@ -405,7 +409,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
detachedByteOut = new ByteArrayOutputStream();
|
||||
OutputStream detachedOut = detachedByteOut;
|
||||
if (input.isEnableAsciiArmorOutput()) {
|
||||
detachedArmorOut = new ArmoredOutputStream(detachedOut);
|
||||
detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
|
||||
if (input.getVersionHeader() != null) {
|
||||
detachedArmorOut.setHeader("Version", input.getVersionHeader());
|
||||
}
|
||||
@ -485,19 +489,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
||||
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
|
||||
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
|
||||
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
|
||||
PgpSignEncryptResult result =
|
||||
new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log);
|
||||
|
||||
// SignatureSubKeyId can be null.
|
||||
if (input.getSignatureSubKeyId() == null) {
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// Note that the checked key here is the master key, not the signing key
|
||||
// (although these are always the same on Yubikeys)
|
||||
result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase());
|
||||
Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign));
|
||||
return result;
|
||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
|
||||
e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,14 +20,10 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/** This parcel stores the input of one or more PgpSignEncrypt operations.
|
||||
@ -42,7 +38,7 @@ import java.util.List;
|
||||
* left, which will be returned in a byte array as part of the result parcel.
|
||||
*
|
||||
*/
|
||||
public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable {
|
||||
public class SignEncryptParcel extends PgpSignEncryptInputParcel {
|
||||
|
||||
public ArrayList<Uri> mInputUris = new ArrayList<>();
|
||||
public ArrayList<Uri> mOutputUris = new ArrayList<>();
|
||||
@ -53,26 +49,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
|
||||
}
|
||||
|
||||
public SignEncryptParcel(Parcel src) {
|
||||
|
||||
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||
mVersionHeader = src.readString();
|
||||
mEnableAsciiArmorOutput = src.readInt() == 1;
|
||||
mCompressionId = src.readInt();
|
||||
mEncryptionMasterKeyIds = src.createLongArray();
|
||||
mSymmetricPassphrase = src.readParcelable(Passphrase.class.getClassLoader());
|
||||
mSymmetricEncryptionAlgorithm = src.readInt();
|
||||
mSignatureMasterKeyId = src.readLong();
|
||||
mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null;
|
||||
mSignatureHashAlgorithm = src.readInt();
|
||||
mSignaturePassphrase = src.readParcelable(Passphrase.class.getClassLoader());
|
||||
mAdditionalEncryptId = src.readLong();
|
||||
mNfcSignedHash = src.createByteArray();
|
||||
mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null;
|
||||
mFailOnMissingEncryptionKeyIds = src.readInt() == 1;
|
||||
mCharset = src.readString();
|
||||
mCleartextSignature = src.readInt() == 1;
|
||||
mDetachedSignature = src.readInt() == 1;
|
||||
mHiddenRecipients = src.readInt() == 1;
|
||||
super(src);
|
||||
|
||||
mInputUris = src.createTypedArrayList(Uri.CREATOR);
|
||||
mOutputUris = src.createTypedArrayList(Uri.CREATOR);
|
||||
@ -110,34 +87,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mVersionHeader);
|
||||
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
|
||||
dest.writeInt(mCompressionId);
|
||||
dest.writeLongArray(mEncryptionMasterKeyIds);
|
||||
dest.writeParcelable(mSymmetricPassphrase, flags);
|
||||
dest.writeInt(mSymmetricEncryptionAlgorithm);
|
||||
dest.writeLong(mSignatureMasterKeyId);
|
||||
if (mSignatureSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mSignatureHashAlgorithm);
|
||||
dest.writeParcelable(mSignaturePassphrase, flags);
|
||||
dest.writeLong(mAdditionalEncryptId);
|
||||
dest.writeByteArray(mNfcSignedHash);
|
||||
if (mNfcCreationTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcCreationTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
|
||||
dest.writeString(mCharset);
|
||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||
dest.writeInt(mHiddenRecipients ? 1 : 0);
|
||||
super.writeToParcel(dest, flags);
|
||||
|
||||
dest.writeTypedList(mInputUris);
|
||||
dest.writeTypedList(mOutputUris);
|
||||
|
@ -1165,7 +1165,7 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
// If anything changed, save the updated (sub)key
|
||||
// If anything change, save the updated (sub)key
|
||||
if (modified != resultKey) {
|
||||
result = replacePublicKey(result, modified);
|
||||
}
|
||||
|
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
public class CryptoInputParcelCacheService extends Service {
|
||||
|
||||
public static final String ACTION_ADD = Constants.INTENT_PREFIX + "ADD";
|
||||
public static final String ACTION_GET = Constants.INTENT_PREFIX + "GET";
|
||||
|
||||
public static final String EXTRA_CRYPTO_INPUT_PARCEL = "crypto_input_parcel";
|
||||
public static final String EXTRA_UUID1 = "uuid1";
|
||||
public static final String EXTRA_UUID2 = "uuid2";
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
|
||||
private static final int MSG_GET_OKAY = 1;
|
||||
private static final int MSG_GET_NOT_FOUND = 2;
|
||||
|
||||
Context mContext;
|
||||
|
||||
private static final UUID NULL_UUID = new UUID(0, 0);
|
||||
|
||||
private ConcurrentHashMap<UUID, CryptoInputParcel> mCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static class InputParcelNotFound extends Exception {
|
||||
public InputParcelNotFound() {
|
||||
}
|
||||
|
||||
public InputParcelNotFound(String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCryptoInputParcel(Context context, Intent data, CryptoInputParcel inputParcel) {
|
||||
UUID mTicket = addCryptoInputParcel(context, inputParcel);
|
||||
// And write out the UUID most and least significant bits.
|
||||
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID1, mTicket.getMostSignificantBits());
|
||||
data.putExtra(OpenPgpApi.EXTRA_CALL_UUID2, mTicket.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static CryptoInputParcel getCryptoInputParcel(Context context, Intent data) {
|
||||
if (!data.getExtras().containsKey(OpenPgpApi.EXTRA_CALL_UUID1)
|
||||
|| !data.getExtras().containsKey(OpenPgpApi.EXTRA_CALL_UUID2)) {
|
||||
return null;
|
||||
}
|
||||
long mostSig = data.getLongExtra(OpenPgpApi.EXTRA_CALL_UUID1, 0);
|
||||
long leastSig = data.getLongExtra(OpenPgpApi.EXTRA_CALL_UUID2, 0);
|
||||
UUID uuid = new UUID(mostSig, leastSig);
|
||||
try {
|
||||
return getCryptoInputParcel(context, uuid);
|
||||
} catch (InputParcelNotFound inputParcelNotFound) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static UUID addCryptoInputParcel(Context context, CryptoInputParcel inputParcel) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
Intent intent = new Intent(context, CryptoInputParcelCacheService.class);
|
||||
intent.setAction(ACTION_ADD);
|
||||
intent.putExtra(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
|
||||
intent.putExtra(EXTRA_UUID1, uuid.getMostSignificantBits());
|
||||
intent.putExtra(EXTRA_UUID2, uuid.getLeastSignificantBits());
|
||||
context.startService(intent);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private static CryptoInputParcel getCryptoInputParcel(Context context, UUID uuid) throws InputParcelNotFound {
|
||||
Intent intent = new Intent(context, CryptoInputParcelCacheService.class);
|
||||
intent.setAction(ACTION_GET);
|
||||
|
||||
final Object mutex = new Object();
|
||||
final Message returnMessage = Message.obtain();
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread("getParcelableThread");
|
||||
handlerThread.start();
|
||||
Handler returnHandler = new Handler(handlerThread.getLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// copy over result to handle after mutex.wait
|
||||
returnMessage.what = message.what;
|
||||
returnMessage.copyFrom(message);
|
||||
synchronized (mutex) {
|
||||
mutex.notify();
|
||||
}
|
||||
// quit handlerThread
|
||||
getLooper().quit();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
intent.putExtra(EXTRA_UUID1, uuid.getMostSignificantBits());
|
||||
intent.putExtra(EXTRA_UUID2, uuid.getLeastSignificantBits());
|
||||
intent.putExtra(EXTRA_MESSENGER, messenger);
|
||||
// send intent to this service
|
||||
context.startService(intent);
|
||||
|
||||
// Wait on mutex until parcelable is returned to handlerThread. Note that this local
|
||||
// variable is used in the handler closure above, so it does make sense here!
|
||||
// noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (mutex) {
|
||||
try {
|
||||
mutex.wait(3000);
|
||||
} catch (InterruptedException e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
|
||||
switch (returnMessage.what) {
|
||||
case MSG_GET_OKAY:
|
||||
Bundle returnData = returnMessage.getData();
|
||||
returnData.setClassLoader(context.getClassLoader());
|
||||
return returnData.getParcelable(EXTRA_CRYPTO_INPUT_PARCEL);
|
||||
case MSG_GET_NOT_FOUND:
|
||||
throw new InputParcelNotFound();
|
||||
default:
|
||||
Log.e(Constants.TAG, "timeout!");
|
||||
throw new InputParcelNotFound("should not happen!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed when service is started by intent
|
||||
*/
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
if (intent == null || intent.getAction() == null) {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_ADD: {
|
||||
long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
|
||||
long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
|
||||
UUID uuid = new UUID(uuid1, uuid2);
|
||||
CryptoInputParcel inputParcel = intent.getParcelableExtra(EXTRA_CRYPTO_INPUT_PARCEL);
|
||||
mCache.put(uuid, inputParcel);
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_GET: {
|
||||
long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
|
||||
long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
|
||||
UUID uuid = new UUID(uuid1, uuid2);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
|
||||
Message msg = Message.obtain();
|
||||
// UUID.equals isn't well documented; we use compareTo instead.
|
||||
if (NULL_UUID.compareTo(uuid) == 0) {
|
||||
msg.what = MSG_GET_NOT_FOUND;
|
||||
} else {
|
||||
CryptoInputParcel inputParcel = mCache.get(uuid);
|
||||
mCache.remove(uuid);
|
||||
msg.what = MSG_GET_OKAY;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
|
||||
msg.setData(bundle);
|
||||
}
|
||||
|
||||
try {
|
||||
messenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "CryptoInputParcelCacheService: Sending message failed", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log.e(Constants.TAG, "CryptoInputParcelCacheService: Intent or Intent Action not supported!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCache.size() <= 0) {
|
||||
// stop whole service if cache is empty
|
||||
Log.d(Constants.TAG, "CryptoInputParcelCacheService: No passphrases remaining in memory, stopping service!");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = this;
|
||||
Log.d(Constants.TAG, "CryptoInputParcelCacheService, onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.d(Constants.TAG, "CryptoInputParcelCacheService, onDestroy()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
public class CryptoInputParcelCacheServiceBinder extends Binder {
|
||||
public CryptoInputParcelCacheService getService() {
|
||||
return CryptoInputParcelCacheService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private final IBinder mBinder = new CryptoInputParcelCacheServiceBinder();
|
||||
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@ -31,16 +32,15 @@ import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
@ -48,8 +48,10 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
|
||||
import org.sufficientlysecure.keychain.ui.NfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
@ -60,7 +62,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
@ -78,9 +79,6 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
* @param encryptionUserIds
|
||||
* @return
|
||||
*/
|
||||
private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
|
||||
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
|
||||
@ -163,52 +161,35 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
}
|
||||
|
||||
private Intent returnPassphraseIntent(Intent data, long keyId) {
|
||||
// build PendingIntent for passphrase input
|
||||
Intent intent = new Intent(getBaseContext(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, keyId);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_DATA, data);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
private static PendingIntent getRequiredInputPendingIntent(Context context,
|
||||
Intent data, RequiredInputParcel requiredInput) {
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
switch (requiredInput.mType) {
|
||||
case NFC_DECRYPT:
|
||||
case NFC_SIGN: {
|
||||
// build PendingIntent for YubiKey NFC operations
|
||||
Intent intent = new Intent(context, NfcOperationActivity.class);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, data);
|
||||
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
return PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent getNfcSignPendingIntent(Intent data, long keyId, Passphrase pin, byte[] hashToSign, int hashAlgo) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(getBaseContext(), NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_SIGN_HASH);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, data);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
|
||||
case PASSPHRASE: {
|
||||
// build PendingIntent for Passphrase request
|
||||
Intent intent = new Intent(context, PassphraseDialogActivity.class);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SERVICE_INTENT, data);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
return PendingIntent.getActivity(context, 0, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
|
||||
return PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
default:
|
||||
throw new AssertionError("Unhandled required input type!");
|
||||
}
|
||||
|
||||
private PendingIntent getNfcDecryptPendingIntent(Intent data, long subKeyId, Passphrase pin, byte[] encryptedSessionKey) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(getBaseContext(), NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, data);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId);
|
||||
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey);
|
||||
return PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
private PendingIntent getKeyserverPendingIntent(Intent data, long masterKeyId) {
|
||||
@ -240,17 +221,13 @@ public class OpenPgpService extends RemoteService {
|
||||
try {
|
||||
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
Passphrase passphrase = null;
|
||||
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
|
||||
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||
}
|
||||
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
if (nfcSignedHash != null) {
|
||||
Log.d(Constants.TAG, "nfcSignedHash:" + Hex.toHexString(nfcSignedHash));
|
||||
} else {
|
||||
Log.d(Constants.TAG, "nfcSignedHash: null");
|
||||
}
|
||||
// sign-only
|
||||
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel()
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setCleartextSignature(cleartextSign)
|
||||
.setDetachedSignature(!cleartextSign)
|
||||
.setVersionHeader(null)
|
||||
.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
Intent signKeyIdIntent = getSignKeyMasterId(data);
|
||||
// NOTE: Fallback to return account settings (Old API)
|
||||
@ -258,17 +235,21 @@ public class OpenPgpService extends RemoteService {
|
||||
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
|
||||
return signKeyIdIntent;
|
||||
}
|
||||
|
||||
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
||||
if (signKeyId == Constants.key.none) {
|
||||
Log.e(Constants.TAG, "No signing key given!");
|
||||
}
|
||||
throw new Exception("No signing key given");
|
||||
} else {
|
||||
pseInput.setSignatureMasterKeyId(signKeyId);
|
||||
|
||||
// carefully: only set if timestamp exists
|
||||
Date nfcCreationDate = null;
|
||||
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
|
||||
Log.d(Constants.TAG, "nfcCreationTimestamp: " + nfcCreationTimestamp);
|
||||
if (nfcCreationTimestamp != -1) {
|
||||
nfcCreationDate = new Date(nfcCreationTimestamp);
|
||||
// get first usable subkey capable of signing
|
||||
try {
|
||||
long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
|
||||
pseInput.getSignatureMasterKeyId()).getSecretSignId();
|
||||
pseInput.setSignatureSubKeyId(signSubKeyId);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
throw new Exception("signing subkey not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
@ -281,42 +262,31 @@ public class OpenPgpService extends RemoteService {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
// sign-only
|
||||
PgpSignEncryptInput pseInput = new PgpSignEncryptInput()
|
||||
.setSignaturePassphrase(passphrase)
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setCleartextSignature(cleartextSign)
|
||||
.setDetachedSignature(!cleartextSign)
|
||||
.setVersionHeader(null)
|
||||
.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
|
||||
.setSignatureMasterKeyId(signKeyId)
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate);
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
}
|
||||
// override passphrase in input parcel if given by API call
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||
}
|
||||
|
||||
// execute PGP operation!
|
||||
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
|
||||
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputData, os);
|
||||
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, os);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
if (pgpResult.getDetachedSignature() != null && !cleartextSign) {
|
||||
@ -372,11 +342,6 @@ public class OpenPgpService extends RemoteService {
|
||||
compressionId = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||
}
|
||||
|
||||
Passphrase passphrase = null;
|
||||
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
|
||||
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||
}
|
||||
|
||||
// first try to get key ids from non-ambiguous key id extra
|
||||
long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||
if (keyIds == null) {
|
||||
@ -401,9 +366,8 @@ public class OpenPgpService extends RemoteService {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength, originalFilename);
|
||||
|
||||
PgpSignEncryptInput pseInput = new PgpSignEncryptInput();
|
||||
pseInput.setSignaturePassphrase(passphrase)
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
|
||||
pseInput.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setVersionHeader(null)
|
||||
.setCompressionId(compressionId)
|
||||
.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED)
|
||||
@ -420,49 +384,49 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
|
||||
if (signKeyId == Constants.key.none) {
|
||||
Log.e(Constants.TAG, "No signing key given!");
|
||||
}
|
||||
throw new Exception("No signing key given");
|
||||
} else {
|
||||
pseInput.setSignatureMasterKeyId(signKeyId);
|
||||
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
// carefully: only set if timestamp exists
|
||||
Date nfcCreationDate = null;
|
||||
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
|
||||
if (nfcCreationTimestamp != -1) {
|
||||
nfcCreationDate = new Date(nfcCreationTimestamp);
|
||||
// get first usable subkey capable of signing
|
||||
try {
|
||||
long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
|
||||
pseInput.getSignatureMasterKeyId()).getSecretSignId();
|
||||
pseInput.setSignatureSubKeyId(signSubKeyId);
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
throw new Exception("signing subkey not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// sign and encrypt
|
||||
pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
|
||||
.setSignatureMasterKeyId(signKeyId)
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate)
|
||||
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
|
||||
}
|
||||
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
}
|
||||
// override passphrase in input parcel if given by API call
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||
}
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
|
||||
|
||||
// execute PGP operation!
|
||||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputData, os);
|
||||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, os);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
@ -511,11 +475,6 @@ public class OpenPgpService extends RemoteService {
|
||||
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
}
|
||||
|
||||
Passphrase passphrase = null;
|
||||
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
|
||||
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||
}
|
||||
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds;
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
||||
@ -533,42 +492,37 @@ public class OpenPgpService extends RemoteService {
|
||||
this, new ProviderHelper(getContext()), null, inputData, os
|
||||
);
|
||||
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
inputParcel = new CryptoInputParcel();
|
||||
}
|
||||
// override passphrase in input parcel if given by API call
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||
}
|
||||
|
||||
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
||||
|
||||
// allow only private keys associated with accounts of this app
|
||||
// no support for symmetric encryption
|
||||
builder.setPassphrase(passphrase)
|
||||
.setAllowSymmetricDecryption(false)
|
||||
builder.setAllowSymmetricDecryption(false)
|
||||
.setAllowedKeyIds(allowedKeyIds)
|
||||
.setDecryptMetadataOnly(decryptMetadataOnly)
|
||||
.setNfcState(nfcDecryptedSessionKey)
|
||||
.setDetachedSignature(detachedSignature);
|
||||
|
||||
DecryptVerifyResult pgpResult = builder.build().execute();
|
||||
DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
throw new PgpGeneralException(
|
||||
"Decryption of symmetric content not supported by API!");
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
// prepare and return PendingIntent to be executed by client
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
getNfcDecryptPendingIntent(data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
|
||||
@ -754,7 +708,6 @@ public class OpenPgpService extends RemoteService {
|
||||
* - has supported API version
|
||||
* - is allowed to call the service (access has been granted)
|
||||
*
|
||||
* @param data
|
||||
* @return null if everything is okay, or a Bundle with an error/PendingIntent
|
||||
*/
|
||||
private Intent checkRequirements(Intent data) {
|
||||
@ -794,9 +747,7 @@ public class OpenPgpService extends RemoteService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: multi-threading
|
||||
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
|
||||
|
||||
@Override
|
||||
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
try {
|
||||
@ -806,30 +757,42 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
String action = data.getAction();
|
||||
if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_SIGN.equals(action)) {
|
||||
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
||||
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
||||
return signImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, false);
|
||||
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, false);
|
||||
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
return decryptAndVerifyImpl(data, input, output, false);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) {
|
||||
return decryptAndVerifyImpl(data, input, output, true);
|
||||
} else if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(action)) {
|
||||
return getSignKeyIdImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
|
||||
return getKeyIdsImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
|
||||
return getKeyImpl(data);
|
||||
} else {
|
||||
return null;
|
||||
switch (action) {
|
||||
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
||||
return signImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_SIGN: {
|
||||
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
||||
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
||||
return signImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_DETACHED_SIGN: {
|
||||
return signImpl(data, input, output, false);
|
||||
}
|
||||
case OpenPgpApi.ACTION_ENCRYPT: {
|
||||
return encryptAndSignImpl(data, input, output, false);
|
||||
}
|
||||
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
|
||||
return encryptAndSignImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
|
||||
return decryptAndVerifyImpl(data, input, output, false);
|
||||
}
|
||||
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
|
||||
return decryptAndVerifyImpl(data, input, output, true);
|
||||
}
|
||||
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
|
||||
return getSignKeyIdImpl(data);
|
||||
}
|
||||
case OpenPgpApi.ACTION_GET_KEY_IDS: {
|
||||
return getKeyIdsImpl(data);
|
||||
}
|
||||
case OpenPgpApi.ACTION_GET_KEY: {
|
||||
return getKeyImpl(data);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// always close input and output file descriptors even in error cases
|
||||
|
@ -31,7 +31,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AccountSettingsActivity extends BaseActivity {
|
||||
|
@ -40,9 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
@ -22,10 +22,14 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,6 @@ import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
@ -68,6 +67,7 @@ import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
@ -159,8 +159,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// decrypt/verify
|
||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_PASSPHRASE = "passphrase";
|
||||
public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
|
||||
|
||||
// keybase proof
|
||||
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
|
||||
@ -169,6 +167,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
// save keyring
|
||||
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
||||
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
||||
|
||||
// delete keyring(s)
|
||||
public static final String DELETE_KEY_LIST = "delete_list";
|
||||
@ -193,7 +192,7 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// promote key
|
||||
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
|
||||
public static final String PROMOTE_TYPE = "promote_type";
|
||||
public static final String PROMOTE_CARD_AID = "promote_card_aid";
|
||||
|
||||
// consolidate
|
||||
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
||||
@ -260,11 +259,12 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// Input
|
||||
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
|
||||
|
||||
// Operation
|
||||
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
|
||||
CertifyResult result = op.certify(parcel, keyServerUri);
|
||||
CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
@ -289,27 +289,20 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
case ACTION_DECRYPT_METADATA: {
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
/* Input */
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
|
||||
/* Operation */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this, inputData, null
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setDecryptMetadataOnly(true)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
.setDecryptMetadataOnly(true);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
|
||||
|
||||
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
|
||||
} catch (Exception e) {
|
||||
@ -384,7 +377,8 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
);
|
||||
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(
|
||||
new CryptoInputParcel());
|
||||
outStream.close();
|
||||
|
||||
if (!decryptVerifyResult.success()) {
|
||||
@ -419,15 +413,13 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
case ACTION_DECRYPT_VERIFY: {
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
/* Input */
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
OutputStream outStream = createCryptOutputStream(data);
|
||||
|
||||
/* Operation */
|
||||
|
||||
/* Operation */
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
@ -436,24 +428,22 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
this, new ProviderHelper(this), this,
|
||||
inputData, outStream
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
builder.setAllowSymmetricDecryption(true);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
|
||||
|
||||
outStream.close();
|
||||
|
||||
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
|
||||
|
||||
/* Output */
|
||||
|
||||
/* Output */
|
||||
finalizeDecryptOutputStream(data, resultData, outStream);
|
||||
|
||||
Log.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(MessageStatus.OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
|
||||
} catch (IOException | PgpGeneralException e) {
|
||||
// TODO get rid of this!
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
@ -478,11 +468,11 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// Input
|
||||
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
||||
Passphrase passphrase = data.getParcelable(EDIT_KEYRING_PASSPHRASE);
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
// Operation
|
||||
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
EditKeyResult result = op.execute(saveParcel, passphrase);
|
||||
OperationResult result = op.execute(saveParcel, cryptoInput);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
@ -492,11 +482,12 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
case ACTION_PROMOTE_KEYRING: {
|
||||
|
||||
// Input
|
||||
long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
|
||||
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
|
||||
|
||||
// Operation
|
||||
PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
PromoteKeyResult result = op.execute(keyRingId);
|
||||
PromoteKeyResult result = op.execute(keyRingId, cardAid);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
@ -553,11 +544,12 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
// Input
|
||||
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
// Operation
|
||||
SignEncryptOperation op = new SignEncryptOperation(
|
||||
this, new ProviderHelper(this), this, mActionCanceled);
|
||||
SignEncryptResult result = op.execute(inputParcel);
|
||||
SignEncryptResult result = op.execute(inputParcel, cryptoInput);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||
|
@ -60,18 +60,18 @@ import java.util.Date;
|
||||
*
|
||||
* Caching behavior for subkeys depends on the cacheSubs preference:
|
||||
*
|
||||
* - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The
|
||||
* checks for special subkeys will still be done, but otherwise it is assumed that all subkeys
|
||||
* from the same master key will use the same passphrase. This can lead to bad passphrase
|
||||
* errors if two subkeys are encrypted differently. This is the default behavior.
|
||||
* - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The
|
||||
* checks for special subkeys will still be done, but otherwise it is assumed that all subkeys
|
||||
* from the same master key will use the same passphrase. This can lead to bad passphrase
|
||||
* errors if two subkeys are encrypted differently. This is the default behavior.
|
||||
*
|
||||
* - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring
|
||||
* has two subkeys for different purposes, passphrases will be cached independently and the
|
||||
* user will be asked for a passphrase once per subkey even if it is the same one. This mode
|
||||
* of operation is more precise, since we can assume that all passphrases returned from cache
|
||||
* will be correct without fail. Since keyrings with differently encrypted subkeys are a very
|
||||
* rare occurrence, and caching by keyring is what the user expects in the vast majority of
|
||||
* cases, this is not the default behavior.
|
||||
* - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring
|
||||
* has two subkeys for different purposes, passphrases will be cached independently and the
|
||||
* user will be asked for a passphrase once per subkey even if it is the same one. This mode
|
||||
* of operation is more precise, since we can assume that all passphrases returned from cache
|
||||
* will be correct without fail. Since keyrings with differently encrypted subkeys are a very
|
||||
* rare occurrence, and caching by keyring is what the user expects in the vast majority of
|
||||
* cases, this is not the default behavior.
|
||||
*
|
||||
*/
|
||||
public class PassphraseCacheService extends Service {
|
||||
@ -123,7 +123,7 @@ public class PassphraseCacheService extends Service {
|
||||
public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId,
|
||||
Passphrase passphrase,
|
||||
String primaryUserId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + masterKeyId);
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.addCachedPassphrase() for " + masterKeyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
||||
@ -137,10 +137,23 @@ public class PassphraseCacheService extends Service {
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
public static void clearCachedPassphrase(Context context, long masterKeyId, long subKeyId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase() for " + masterKeyId);
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
|
||||
intent.putExtra(EXTRA_KEY_ID, masterKeyId);
|
||||
intent.putExtra(EXTRA_SUBKEY_ID, subKeyId);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||
* designed to wait until the service returns the passphrase.
|
||||
|
||||
*
|
||||
* @return passphrase or null (if no passphrase is cached for this keyId)
|
||||
*/
|
||||
public static Passphrase getCachedPassphrase(Context context, long masterKeyId, long subKeyId) throws KeyNotFoundException {
|
||||
@ -218,7 +231,7 @@ public class PassphraseCacheService extends Service {
|
||||
}
|
||||
|
||||
// on "none" key, just do nothing
|
||||
if(masterKeyId == Constants.key.none) {
|
||||
if (masterKeyId == Constants.key.none) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -232,11 +245,11 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
switch (keyType) {
|
||||
case DIVERT_TO_CARD:
|
||||
if (Preferences.getPreferences(this).useDefaultYubikeyPin()) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Using default Yubikey PIN: 123456");
|
||||
return new Passphrase("123456"); // default Yubikey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
|
||||
if (Preferences.getPreferences(this).useDefaultYubiKeyPin()) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: Using default YubiKey PIN: 123456");
|
||||
return new Passphrase("123456"); // default YubiKey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
|
||||
} else {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: NOT using default Yubikey PIN");
|
||||
Log.d(Constants.TAG, "PassphraseCacheService: NOT using default YubiKey PIN");
|
||||
break;
|
||||
}
|
||||
case PASSPHRASE_EMPTY:
|
||||
@ -310,11 +323,11 @@ public class PassphraseCacheService extends Service {
|
||||
/**
|
||||
* Build pending intent that is executed by alarm manager to time out a specific passphrase
|
||||
*/
|
||||
private static PendingIntent buildIntent(Context context, long keyId) {
|
||||
private static PendingIntent buildIntent(Context context, long referenceKeyId) {
|
||||
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(EXTRA_KEY_ID, referenceKeyId);
|
||||
// request code should be unique for each PendingIntent, thus keyId is used
|
||||
return PendingIntent.getBroadcast(context, (int) keyId, intent,
|
||||
return PendingIntent.getBroadcast(context, (int) referenceKeyId, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
|
||||
@ -325,11 +338,17 @@ public class PassphraseCacheService extends Service {
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()");
|
||||
|
||||
if (intent == null || intent.getAction() == null) {
|
||||
updateService();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
// register broadcastreceiver
|
||||
registerReceiver();
|
||||
|
||||
if (intent != null && intent.getAction() != null) {
|
||||
if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_PASSPHRASE_CACHE_ADD: {
|
||||
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
|
||||
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
|
||||
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, -1);
|
||||
@ -343,28 +362,19 @@ public class PassphraseCacheService extends Service {
|
||||
);
|
||||
|
||||
// if we don't cache by specific subkey id, or the requested subkey is the master key,
|
||||
// just add master key id to the cache
|
||||
if (subKeyId == masterKeyId || !Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
|
||||
mPassphraseCache.put(masterKeyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, masterKeyId));
|
||||
}
|
||||
} else {
|
||||
// otherwise, add this specific subkey to the cache
|
||||
mPassphraseCache.put(subKeyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, subKeyId));
|
||||
}
|
||||
// just add master key id to the cache, otherwise, add this specific subkey to the cache
|
||||
long referenceKeyId =
|
||||
Preferences.getPreferences(mContext).getPassphraseCacheSubs() ? subKeyId : masterKeyId;
|
||||
mPassphraseCache.put(referenceKeyId, new CachedPassphrase(passphrase, primaryUserID));
|
||||
if (ttl > 0) {
|
||||
// register new alarm with keyId for this passphrase
|
||||
long triggerTime = new Date().getTime() + (ttl * 1000);
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, referenceKeyId));
|
||||
}
|
||||
|
||||
updateService();
|
||||
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
|
||||
break;
|
||||
}
|
||||
case ACTION_PASSPHRASE_CACHE_GET: {
|
||||
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric);
|
||||
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric);
|
||||
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
|
||||
@ -392,22 +402,42 @@ public class PassphraseCacheService extends Service {
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e);
|
||||
}
|
||||
} else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) {
|
||||
break;
|
||||
}
|
||||
case ACTION_PASSPHRASE_CACHE_CLEAR: {
|
||||
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
// Stop all ttl alarms
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) {
|
||||
|
||||
long referenceKeyId;
|
||||
if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
|
||||
referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
|
||||
} else {
|
||||
referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
|
||||
}
|
||||
// Stop specific ttl alarm and
|
||||
am.cancel(buildIntent(this, referenceKeyId));
|
||||
mPassphraseCache.delete(referenceKeyId);
|
||||
|
||||
} else {
|
||||
|
||||
// Stop all ttl alarms
|
||||
for (int i = 0; i < mPassphraseCache.size(); i++) {
|
||||
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
|
||||
}
|
||||
mPassphraseCache.clear();
|
||||
|
||||
}
|
||||
|
||||
mPassphraseCache.clear();
|
||||
|
||||
updateService();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateService();
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
@ -82,10 +82,14 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mRevokeSubKeys = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return isRestrictedOnly() && mChangeSubKeys.isEmpty();
|
||||
}
|
||||
|
||||
/** Returns true iff this parcel does not contain any operations which require a passphrase. */
|
||||
public boolean isRestrictedOnly() {
|
||||
if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty()
|
||||
|| !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty()
|
||||
|| !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeUserIds.isEmpty()
|
||||
|| !mRevokeSubKeys.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@ -26,6 +27,7 @@ import android.support.v4.app.FragmentManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.input;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
/**
|
||||
* This is a base class for the input of crypto operations.
|
||||
*/
|
||||
public class CryptoInputParcel implements Parcelable {
|
||||
|
||||
final Date mSignatureTime;
|
||||
final Passphrase mPassphrase;
|
||||
|
||||
// this map contains both decrypted session keys and signed hashes to be
|
||||
// used in the crypto operation described by this parcel.
|
||||
private HashMap<ByteBuffer, byte[]> mCryptoData = new HashMap<>();
|
||||
|
||||
public CryptoInputParcel() {
|
||||
mSignatureTime = new Date();
|
||||
mPassphrase = null;
|
||||
}
|
||||
|
||||
public CryptoInputParcel(Date signatureTime, Passphrase passphrase) {
|
||||
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public CryptoInputParcel(Passphrase passphrase) {
|
||||
mSignatureTime = new Date();
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public CryptoInputParcel(Date signatureTime) {
|
||||
mSignatureTime = signatureTime == null ? new Date() : signatureTime;
|
||||
mPassphrase = null;
|
||||
}
|
||||
|
||||
protected CryptoInputParcel(Parcel source) {
|
||||
mSignatureTime = new Date(source.readLong());
|
||||
mPassphrase = source.readParcelable(getClass().getClassLoader());
|
||||
|
||||
{
|
||||
int count = source.readInt();
|
||||
mCryptoData = new HashMap<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
byte[] key = source.createByteArray();
|
||||
byte[] value = source.createByteArray();
|
||||
mCryptoData.put(ByteBuffer.wrap(key), value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(mSignatureTime.getTime());
|
||||
dest.writeParcelable(mPassphrase, 0);
|
||||
|
||||
dest.writeInt(mCryptoData.size());
|
||||
for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) {
|
||||
dest.writeByteArray(entry.getKey().array());
|
||||
dest.writeByteArray(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void addCryptoData(byte[] hash, byte[] signedHash) {
|
||||
mCryptoData.put(ByteBuffer.wrap(hash), signedHash);
|
||||
}
|
||||
|
||||
public Map<ByteBuffer, byte[]> getCryptoData() {
|
||||
return Collections.unmodifiableMap(mCryptoData);
|
||||
}
|
||||
|
||||
public Date getSignatureTime() {
|
||||
return mSignatureTime;
|
||||
}
|
||||
|
||||
public boolean hasPassphrase() {
|
||||
return mPassphrase != null;
|
||||
}
|
||||
|
||||
public Passphrase getPassphrase() {
|
||||
return mPassphrase;
|
||||
}
|
||||
|
||||
public static final Creator<CryptoInputParcel> CREATOR = new Creator<CryptoInputParcel>() {
|
||||
public CryptoInputParcel createFromParcel(final Parcel source) {
|
||||
return new CryptoInputParcel(source);
|
||||
}
|
||||
|
||||
public CryptoInputParcel[] newArray(final int size) {
|
||||
return new CryptoInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("CryptoInput: { ");
|
||||
b.append(mSignatureTime).append(" ");
|
||||
if (mPassphrase != null) {
|
||||
b.append("passphrase");
|
||||
}
|
||||
if (mCryptoData != null) {
|
||||
b.append(mCryptoData.size());
|
||||
b.append(" hashes ");
|
||||
}
|
||||
b.append("}");
|
||||
return b.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package org.sufficientlysecure.keychain.service.input;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants.key;
|
||||
|
||||
|
||||
public class RequiredInputParcel implements Parcelable {
|
||||
|
||||
public enum RequiredInputType {
|
||||
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT
|
||||
}
|
||||
|
||||
public Date mSignatureTime;
|
||||
|
||||
public final RequiredInputType mType;
|
||||
|
||||
public final byte[][] mInputHashes;
|
||||
public final int[] mSignAlgos;
|
||||
|
||||
private Long mMasterKeyId;
|
||||
private Long mSubKeyId;
|
||||
|
||||
private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes,
|
||||
int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) {
|
||||
mType = type;
|
||||
mInputHashes = inputHashes;
|
||||
mSignAlgos = signAlgos;
|
||||
mSignatureTime = signatureTime;
|
||||
mMasterKeyId = masterKeyId;
|
||||
mSubKeyId = subKeyId;
|
||||
}
|
||||
|
||||
public RequiredInputParcel(Parcel source) {
|
||||
mType = RequiredInputType.values()[source.readInt()];
|
||||
|
||||
// 0 = none, 1 = both, 2 = only hashes (decrypt)
|
||||
int hashTypes = source.readInt();
|
||||
if (hashTypes != 0) {
|
||||
int count = source.readInt();
|
||||
mInputHashes = new byte[count][];
|
||||
if (hashTypes == 1) {
|
||||
mSignAlgos = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
mInputHashes[i] = source.createByteArray();
|
||||
mSignAlgos[i] = source.readInt();
|
||||
}
|
||||
} else {
|
||||
mSignAlgos = null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
mInputHashes[i] = source.createByteArray();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mInputHashes = null;
|
||||
mSignAlgos = null;
|
||||
}
|
||||
|
||||
mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
mSubKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
|
||||
}
|
||||
|
||||
public Long getMasterKeyId() {
|
||||
return mMasterKeyId;
|
||||
}
|
||||
|
||||
public Long getSubKeyId() {
|
||||
return mSubKeyId;
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createNfcSignOperation(
|
||||
byte[] inputHash, int signAlgo, Date signatureTime) {
|
||||
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
|
||||
new byte[][] { inputHash }, new int[] { signAlgo },
|
||||
signatureTime, null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash, long subKeyId) {
|
||||
return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
|
||||
new byte[][] { inputHash }, null, null, null, subKeyId);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredSignPassphrase(
|
||||
long masterKeyId, long subKeyId, Date signatureTime) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
null, null, signatureTime, masterKeyId, subKeyId);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredDecryptPassphrase(
|
||||
long masterKeyId, long subKeyId) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
null, null, null, masterKeyId, subKeyId);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredSymmetricPassphrase() {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE_SYMMETRIC,
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static RequiredInputParcel createRequiredPassphrase(
|
||||
RequiredInputParcel req) {
|
||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||
null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mType.ordinal());
|
||||
if (mInputHashes != null) {
|
||||
dest.writeInt(mSignAlgos != null ? 1 : 2);
|
||||
dest.writeInt(mInputHashes.length);
|
||||
for (int i = 0; i < mInputHashes.length; i++) {
|
||||
dest.writeByteArray(mInputHashes[i]);
|
||||
if (mSignAlgos != null) {
|
||||
dest.writeInt(mSignAlgos[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mSignatureTime != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureTime.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mMasterKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final Creator<RequiredInputParcel> CREATOR = new Creator<RequiredInputParcel>() {
|
||||
public RequiredInputParcel createFromParcel(final Parcel source) {
|
||||
return new RequiredInputParcel(source);
|
||||
}
|
||||
|
||||
public RequiredInputParcel[] newArray(final int size) {
|
||||
return new RequiredInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static class NfcSignOperationsBuilder {
|
||||
Date mSignatureTime;
|
||||
ArrayList<Integer> mSignAlgos = new ArrayList<>();
|
||||
ArrayList<byte[]> mInputHashes = new ArrayList<>();
|
||||
Long mMasterKeyId;
|
||||
Long mSubKeyId;
|
||||
|
||||
public NfcSignOperationsBuilder(Date signatureTime, Long masterKeyId, Long subKeyId) {
|
||||
mSignatureTime = signatureTime;
|
||||
mMasterKeyId = masterKeyId;
|
||||
mSubKeyId = subKeyId;
|
||||
}
|
||||
|
||||
public RequiredInputParcel build() {
|
||||
byte[][] inputHashes = new byte[mInputHashes.size()][];
|
||||
mInputHashes.toArray(inputHashes);
|
||||
int[] signAlgos = new int[mSignAlgos.size()];
|
||||
for (int i = 0; i < mSignAlgos.size(); i++) {
|
||||
signAlgos[i] = mSignAlgos.get(i);
|
||||
}
|
||||
|
||||
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
|
||||
inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId);
|
||||
}
|
||||
|
||||
public void addHash(byte[] hash, int algo) {
|
||||
mInputHashes.add(hash);
|
||||
mSignAlgos.add(algo);
|
||||
}
|
||||
|
||||
public void addAll(RequiredInputParcel input) {
|
||||
if (!mSignatureTime.equals(input.mSignatureTime)) {
|
||||
throw new AssertionError("input times must match, this is a programming error!");
|
||||
}
|
||||
if (input.mType != RequiredInputType.NFC_SIGN) {
|
||||
throw new AssertionError("operation types must match, this is a progrmming error!");
|
||||
}
|
||||
|
||||
Collections.addAll(mInputHashes, input.mInputHashes);
|
||||
for (int signAlgo : input.mSignAlgos) {
|
||||
mSignAlgos.add(signAlgo);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mInputHashes.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class CertifyFingerprintActivity extends BaseActivity {
|
||||
|
@ -19,6 +19,8 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
/**
|
||||
* Signs the specified public key with the specified secret master key
|
||||
|
@ -25,8 +25,6 @@ import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
@ -56,23 +54,21 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CertifyKeyFragment extends LoaderFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public class CertifyKeyFragment extends CryptoOperationFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
ListView mUserIds;
|
||||
@ -102,9 +98,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setContentShown(false);
|
||||
|
||||
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
|
||||
if (mPubMasterKeyIds == null) {
|
||||
Log.e(Constants.TAG, "List of key ids to certify missing!");
|
||||
@ -114,6 +107,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
|
||||
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
|
||||
KeychainIntentService.EXTRA_MESSENGER);
|
||||
mPassthroughMessenger = null; // TODO remove, development hack
|
||||
|
||||
// preselect certify key id if given
|
||||
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
|
||||
@ -143,9 +137,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
|
||||
View view = inflater.inflate(R.layout.certify_key_fragment, getContainer());
|
||||
View view = inflater.inflate(R.layout.certify_key_fragment, null);
|
||||
|
||||
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
|
||||
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
|
||||
@ -173,7 +165,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
|
||||
Notify.Style.ERROR).show();
|
||||
} else {
|
||||
initiateCertifying();
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -183,7 +175,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
mUploadKeyCheckbox.setChecked(false);
|
||||
}
|
||||
|
||||
return root;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -222,17 +214,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
}) {
|
||||
@Override
|
||||
public byte[] getBlob(int column) {
|
||||
// For some reason, getBlob was not implemented before ICS
|
||||
if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
try {
|
||||
// haha, yes there is int.class
|
||||
Method m = MatrixCursor.class.getDeclaredMethod("get", new Class[]{int.class});
|
||||
m.setAccessible(true);
|
||||
return (byte[]) m.invoke(this, 1);
|
||||
} catch (Exception e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
return super.getBlob(column);
|
||||
}
|
||||
};
|
||||
@ -307,7 +288,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
}
|
||||
|
||||
mUserIdsAdapter.swapCursor(matrix);
|
||||
setContentShown(true, isResumed());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -315,49 +295,8 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
mUserIdsAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateCertifying() {
|
||||
// get the user's passphrase for this key (if required)
|
||||
Passphrase passphrase;
|
||||
try {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), mSignMasterKeyId, mSignMasterKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Key not found!", e);
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
if (passphrase == null) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSignMasterKeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
} else {
|
||||
startCertifying();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
startCertifying();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startCertifying() {
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
// Bail out if there is not at least one user id selected
|
||||
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
|
||||
if (certifyActions.isEmpty()) {
|
||||
@ -372,6 +311,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
|
||||
parcel.mCertifyActions.addAll(certifyActions);
|
||||
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
@ -396,11 +336,17 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
// handle messages by KeychainIntentCryptoServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
|
||||
|
||||
Intent intent = new Intent();
|
||||
|
@ -17,17 +17,25 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CreateKeyActivity extends BaseActivity {
|
||||
public class CreateKeyActivity extends BaseNfcActivity {
|
||||
|
||||
public static final String EXTRA_NAME = "name";
|
||||
public static final String EXTRA_EMAIL = "email";
|
||||
@ -35,6 +43,10 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
|
||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||
|
||||
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
||||
public static final String EXTRA_NFC_AID = "nfc_aid";
|
||||
public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
|
||||
|
||||
public static final String FRAGMENT_TAG = "currentFragment";
|
||||
|
||||
String mName;
|
||||
@ -60,14 +72,29 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
|
||||
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
|
||||
} else {
|
||||
// Initialize members with default values for a new instance
|
||||
mName = getIntent().getStringExtra(EXTRA_NAME);
|
||||
mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
|
||||
mFirstTime = getIntent().getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||
|
||||
// Start with first fragment of wizard
|
||||
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
|
||||
loadFragment(frag, FragAction.START);
|
||||
Intent intent = getIntent();
|
||||
// Initialize members with default values for a new instance
|
||||
mName = intent.getStringExtra(EXTRA_NAME);
|
||||
mEmail = intent.getStringExtra(EXTRA_EMAIL);
|
||||
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||
|
||||
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
|
||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
||||
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
|
||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
|
||||
|
||||
Fragment frag2 = CreateKeyYubiImportFragment.createInstance(
|
||||
nfcFingerprints, nfcAid, nfcUserId);
|
||||
loadFragment(frag2, FragAction.START);
|
||||
|
||||
setTitle(R.string.title_import_keys);
|
||||
return;
|
||||
} else {
|
||||
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
|
||||
loadFragment(frag, FragAction.START);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (mFirstTime) {
|
||||
@ -79,6 +106,38 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNfcPerform() throws IOException {
|
||||
if (mCurrentFragment instanceof NfcListenerFragment) {
|
||||
((NfcListenerFragment) mCurrentFragment).onNfcPerform();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] scannedFingerprints = nfcGetFingerprints();
|
||||
byte[] nfcAid = nfcGetAid();
|
||||
String userId = nfcGetUserId();
|
||||
|
||||
try {
|
||||
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
|
||||
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Intent intent = new Intent(this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Fragment frag = CreateKeyYubiImportFragment.createInstance(
|
||||
scannedFingerprints, nfcAid, userId);
|
||||
loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@ -125,8 +184,14 @@ public class CreateKeyActivity extends BaseActivity {
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
|
||||
}
|
||||
|
||||
interface NfcListenerFragment {
|
||||
public void onNfcPerform() throws IOException;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
if (mAdditionalEmailModels == null) {
|
||||
mAdditionalEmailModels = new ArrayList<>();
|
||||
if (mCreateKeyActivity.mAdditionalEmails != null) {
|
||||
setAdditionalEmails(mCreateKeyActivity.mAdditionalEmails);
|
||||
mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,12 +209,6 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
return emails;
|
||||
}
|
||||
|
||||
private void setAdditionalEmails(ArrayList<String> emails) {
|
||||
for (String email : emails) {
|
||||
mAdditionalEmailModels.add(new EmailAdapter.ViewModel(email));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@ -244,8 +238,7 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
// Provide a reference to the views for each data item
|
||||
// Complex data items may need more than one view per item, and
|
||||
// you provide access to all the views for a data item in a view holder
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
// each data item is just a string in this case
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public TextView mTextView;
|
||||
public ImageButton mDeleteButton;
|
||||
|
||||
@ -289,7 +282,10 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||
if (holder instanceof ViewHolder) {
|
||||
if (holder instanceof FooterHolder) {
|
||||
FooterHolder thisHolder = (FooterHolder) holder;
|
||||
thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
|
||||
} else if (holder instanceof ViewHolder) {
|
||||
ViewHolder thisHolder = (ViewHolder) holder;
|
||||
// - get element from your dataset at this position
|
||||
// - replace the contents of the view with that element
|
||||
@ -302,9 +298,6 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
remove(model);
|
||||
}
|
||||
});
|
||||
} else if (holder instanceof FooterHolder) {
|
||||
FooterHolder thisHolder = (FooterHolder) holder;
|
||||
thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,6 +325,12 @@ public class CreateKeyEmailFragment extends Fragment {
|
||||
notifyItemInserted(mDataset.size() - 1);
|
||||
}
|
||||
|
||||
private void addAll(ArrayList<String> emails) {
|
||||
for (String email : emails) {
|
||||
mDataset.add(new EmailAdapter.ViewModel(email));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(ViewModel model) {
|
||||
int position = mDataset.indexOf(model);
|
||||
mDataset.remove(position);
|
||||
|
@ -78,7 +78,7 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
|
||||
mCreateKey = view.findViewById(R.id.create_key_create_key_button);
|
||||
mImportKey = view.findViewById(R.id.create_key_import_button);
|
||||
// mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
|
||||
mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
|
||||
mCancel = (TextView) view.findViewById(R.id.create_key_cancel);
|
||||
|
||||
if (mCreateKeyActivity.mFirstTime) {
|
||||
@ -95,6 +95,14 @@ public class CreateKeyStartFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
mYubiKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment();
|
||||
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
mImportKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
|
||||
public class CreateKeyYubiImportFragment extends Fragment implements NfcListenerFragment {
|
||||
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_AID = "aid";
|
||||
public static final String ARG_USER_ID = "user_ids";
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
|
||||
private byte[] mNfcFingerprints;
|
||||
private long mNfcMasterKeyId;
|
||||
private byte[] mNfcAid;
|
||||
private String mNfcUserId;
|
||||
private String mNfcFingerprint;
|
||||
private ImportKeysListFragment mListFragment;
|
||||
private TextView vSerNo;
|
||||
private TextView vUserId;
|
||||
|
||||
public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
|
||||
|
||||
CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
|
||||
args.putByteArray(ARG_AID, nfcAid);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||
|
||||
mNfcFingerprints = args.getByteArray(ARG_FINGERPRINT);
|
||||
mNfcAid = args.getByteArray(ARG_AID);
|
||||
mNfcUserId = args.getString(ARG_USER_ID);
|
||||
|
||||
mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
|
||||
mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false);
|
||||
|
||||
vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
|
||||
vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
|
||||
|
||||
{
|
||||
View mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (getFragmentManager().getBackStackEntryCount() == 0) {
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
} else {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
View mNextButton = view.findViewById(R.id.create_key_next_button);
|
||||
mNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
importKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mNfcFingerprint, true);
|
||||
|
||||
view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
refreshSearch();
|
||||
}
|
||||
});
|
||||
|
||||
setData();
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import")
|
||||
.commit();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle args) {
|
||||
super.onSaveInstanceState(args);
|
||||
|
||||
args.putByteArray(ARG_FINGERPRINT, mNfcFingerprints);
|
||||
args.putByteArray(ARG_AID, mNfcAid);
|
||||
args.putString(ARG_USER_ID, mNfcUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
public void setData() {
|
||||
String serno = Hex.toHexString(mNfcAid, 10, 4);
|
||||
vSerNo.setText(getString(R.string.yubikey_serno, serno));
|
||||
|
||||
if (!mNfcUserId.isEmpty()) {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder, mNfcUserId));
|
||||
} else {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder_unset));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshSearch() {
|
||||
mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint,
|
||||
Preferences.getPreferences(getActivity()).getCloudSearchPrefs()));
|
||||
}
|
||||
|
||||
public void importKey() {
|
||||
|
||||
// Message is received after decrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT
|
||||
) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
ImportKeyResult result =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (!result.success()) {
|
||||
result.createNotify(getActivity()).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(getActivity(), ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(mNfcMasterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
|
||||
startActivity(intent);
|
||||
getActivity().finish();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
|
||||
String hexFp = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
|
||||
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||
keyList.add(new ParcelableKeyRing(hexFp, null, null));
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
|
||||
|
||||
{
|
||||
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||
Preferences.CloudSearchPrefs cloudPrefs =
|
||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||
}
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNfcPerform() throws IOException {
|
||||
|
||||
mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints();
|
||||
mNfcAid = mCreateKeyActivity.nfcGetAid();
|
||||
mNfcUserId = mCreateKeyActivity.nfcGetUserId();
|
||||
|
||||
mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
|
||||
mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
|
||||
|
||||
setData();
|
||||
refreshSearch();
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||
|
||||
|
||||
public class CreateKeyYubiWaitFragment extends Fragment {
|
||||
|
||||
CreateKeyActivity mCreateKeyActivity;
|
||||
View mBackButton;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false);
|
||||
|
||||
mBackButton = view.findViewById(R.id.create_key_back_button);
|
||||
|
||||
mBackButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||
}
|
||||
|
||||
}
|
@ -21,12 +21,12 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class DecryptFilesActivity extends BaseActivity {
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
@ -32,13 +33,13 @@ import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
@ -63,6 +64,8 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
private Uri mInputUri = null;
|
||||
private Uri mOutputUri = null;
|
||||
|
||||
private String mCurrentCryptoOperation;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
@ -90,9 +93,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
|
||||
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// reset state
|
||||
mPassphrase = null;
|
||||
mNfcDecryptedSessionKey = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
@ -144,7 +144,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
return;
|
||||
}
|
||||
|
||||
decryptOriginalFilename();
|
||||
startDecryptFilenames();
|
||||
}
|
||||
|
||||
private String removeEncryptedAppend(String name) {
|
||||
@ -157,110 +157,45 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
}
|
||||
|
||||
private void askForOutputFilename(String originalFilename) {
|
||||
String targetName;
|
||||
if (!TextUtils.isEmpty(originalFilename)) {
|
||||
targetName = originalFilename;
|
||||
} else {
|
||||
targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
|
||||
if (TextUtils.isEmpty(originalFilename)) {
|
||||
originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(mInputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
File targetFile = new File(parentDir, targetName);
|
||||
File targetFile = new File(parentDir, originalFilename);
|
||||
FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
|
||||
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
||||
} else {
|
||||
FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
|
||||
FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptOriginalFilename() {
|
||||
Log.d(Constants.TAG, "decryptOriginalFilename");
|
||||
private void startDecrypt() {
|
||||
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA);
|
||||
|
||||
// data
|
||||
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
|
||||
|
||||
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri);
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
|
||||
|
||||
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after decrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_decrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
DecryptVerifyResult pgpResult =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
startPassphraseDialog(Constants.key.symmetric);
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
// go on...
|
||||
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
|
||||
} else {
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
private void startDecryptFilenames() {
|
||||
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
@SuppressLint("HandlerLeak")
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
// use current operation, either decrypt metadata or decrypt payload
|
||||
intent.setAction(mCurrentCryptoOperation);
|
||||
|
||||
// data
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
|
||||
|
||||
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
|
||||
@ -269,8 +204,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
|
||||
|
||||
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@ -280,10 +214,16 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
getString(R.string.progress_decrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
@ -291,39 +231,39 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
DecryptVerifyResult pgpResult =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
startPassphraseDialog(Constants.key.symmetric);
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
if (pgpResult.success()) {
|
||||
|
||||
// display signature result in activity
|
||||
onResult(pgpResult);
|
||||
switch (mCurrentCryptoOperation) {
|
||||
case KeychainIntentService.ACTION_DECRYPT_METADATA: {
|
||||
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
|
||||
break;
|
||||
}
|
||||
case KeychainIntentService.ACTION_DECRYPT_VERIFY: {
|
||||
// display signature result in activity
|
||||
onResult(pgpResult);
|
||||
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
setInputUri(null);
|
||||
}
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
setInputUri(null);
|
||||
}
|
||||
|
||||
/*
|
||||
// A future open after decryption feature
|
||||
if () {
|
||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
||||
viewFile.setInputData(mOutputUri);
|
||||
startActivity(viewFile);
|
||||
/*
|
||||
// A future open after decryption feature
|
||||
if () {
|
||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
||||
viewFile.setInputData(mOutputUri);
|
||||
startActivity(viewFile);
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log.e(Constants.TAG, "Bug: not supported operation!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
}
|
||||
@ -346,22 +286,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
decryptOriginalFilename();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC_DECRYPT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
decryptOriginalFilename();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_INPUT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
setInputUri(data.getData());
|
||||
@ -373,7 +297,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
// This happens after output file was selected, so start our operation
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mOutputUri = data.getData();
|
||||
decryptStart();
|
||||
startDecrypt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -383,4 +307,5 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
@ -30,16 +29,13 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public abstract class DecryptFragment extends Fragment {
|
||||
public abstract class DecryptFragment extends CryptoOperationFragment {
|
||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC_DECRYPT = 0x00008002;
|
||||
|
||||
protected long mSignatureKeyId = 0;
|
||||
|
||||
protected LinearLayout mResultLayout;
|
||||
@ -56,11 +52,6 @@ public abstract class DecryptFragment extends Fragment {
|
||||
protected TextView mSignatureEmail;
|
||||
protected TextView mSignatureAction;
|
||||
|
||||
|
||||
// State
|
||||
protected Passphrase mPassphrase;
|
||||
protected byte[] mNfcDecryptedSessionKey;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
@ -95,25 +86,6 @@ public abstract class DecryptFragment extends Fragment {
|
||||
startActivity(viewKeyIntent);
|
||||
}
|
||||
|
||||
protected void startPassphraseDialog(long subkeyId) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
}
|
||||
|
||||
protected void startNfcDecrypt(long subKeyId, Passphrase pin, byte[] encryptedSessionKey) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(getActivity(), NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY);
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey);
|
||||
|
||||
startActivityForResult(intent, REQUEST_CODE_NFC_DECRYPT);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return returns false if signature is invalid, key is revoked or expired.
|
||||
@ -253,9 +225,4 @@ public abstract class DecryptFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden by MessageFragment and FileFragment to start actual decryption
|
||||
*/
|
||||
protected abstract void decryptStart();
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@ -30,7 +29,6 @@ import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
@ -38,6 +36,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -51,10 +50,7 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
// view
|
||||
private LinearLayout mValidLayout;
|
||||
private LinearLayout mInvalidLayout;
|
||||
private Button mInvalidButton;
|
||||
private TextView mText;
|
||||
private View mShareButton;
|
||||
private View mCopyButton;
|
||||
|
||||
// model
|
||||
private String mCiphertext;
|
||||
@ -81,23 +77,26 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
|
||||
mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid);
|
||||
mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid);
|
||||
mInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
|
||||
mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
|
||||
mShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
|
||||
mCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext);
|
||||
mShareButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
View vShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
|
||||
vShareButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(sendWithChooserExcludingEncrypt(mText.getText().toString()));
|
||||
}
|
||||
});
|
||||
mCopyButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
View vCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext);
|
||||
vCopyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
copyToClipboard(mText.getText().toString());
|
||||
}
|
||||
});
|
||||
mInvalidButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
Button vInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
|
||||
vInvalidButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mInvalidLayout.setVisibility(View.GONE);
|
||||
@ -143,14 +142,12 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
|
||||
if (ciphertext != null) {
|
||||
mCiphertext = ciphertext;
|
||||
decryptStart();
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
@ -160,10 +157,10 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// data
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
|
||||
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
@ -177,6 +174,11 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
@ -184,20 +186,7 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
DecryptVerifyResult pgpResult =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
startPassphraseDialog(Constants.key.symmetric);
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
if (pgpResult.success()) {
|
||||
|
||||
byte[] decryptedMessage = returnData
|
||||
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
|
||||
@ -245,34 +234,4 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
decryptStart();
|
||||
} else {
|
||||
getActivity().finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC_DECRYPT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
decryptStart();
|
||||
} else {
|
||||
getActivity().finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import android.os.Bundle;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class EditKeyActivity extends BaseActivity {
|
||||
|
@ -51,27 +51,27 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.*;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
public class EditKeyFragment extends CryptoOperationFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
|
||||
private ListView mUserIdsList;
|
||||
private ListView mSubkeysList;
|
||||
private ListView mUserIdsAddedList;
|
||||
@ -96,7 +96,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
|
||||
private String mPrimaryUserId;
|
||||
private Passphrase mCurrentPassphrase;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
@ -125,8 +124,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.edit_key_fragment, getContainer());
|
||||
View view = inflater.inflate(R.layout.edit_key_fragment, null);
|
||||
|
||||
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
|
||||
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys);
|
||||
@ -136,7 +134,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id);
|
||||
mAddSubkey = view.findViewById(R.id.edit_key_action_add_key);
|
||||
|
||||
return root;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -151,7 +149,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
if (mDataUri == null) {
|
||||
returnKeyringParcel();
|
||||
} else {
|
||||
saveInDatabase(mCurrentPassphrase);
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
}
|
||||
}, new OnClickListener() {
|
||||
@ -181,18 +179,12 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) {
|
||||
mSaveKeyringParcel = saveKeyringParcel;
|
||||
mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId;
|
||||
if (saveKeyringParcel.mNewUnlock != null) {
|
||||
mCurrentPassphrase = saveKeyringParcel.mNewUnlock.mNewPassphrase;
|
||||
}
|
||||
|
||||
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true);
|
||||
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true);
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
|
||||
// show directly
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
@ -212,9 +204,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
case GNU_DUMMY:
|
||||
finishWithError(LogType.MSG_EK_ERROR_DUMMY);
|
||||
return;
|
||||
case DIVERT_TO_CARD:
|
||||
finishWithError(LogType.MSG_EK_ERROR_DIVERT);
|
||||
break;
|
||||
}
|
||||
|
||||
mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
|
||||
@ -225,24 +214,10 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(),
|
||||
mSaveKeyringParcel.mMasterKeyId, mSaveKeyringParcel.mMasterKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentPassphrase == null) {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSaveKeyringParcel.mMasterKeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
} else {
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
|
||||
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
|
||||
}
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
|
||||
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
|
||||
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel);
|
||||
mUserIdsList.setAdapter(mUserIdsAdapter);
|
||||
@ -258,28 +233,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mCurrentPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
|
||||
getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
|
||||
} else {
|
||||
getActivity().finish();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
mChangePassphrase.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -318,7 +271,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
setContentShown(false);
|
||||
|
||||
switch (id) {
|
||||
case LOADER_ID_USER_IDS: {
|
||||
@ -351,7 +303,6 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
break;
|
||||
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -393,7 +344,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
|
||||
messenger, mCurrentPassphrase, R.string.title_change_passphrase);
|
||||
messenger, R.string.title_change_passphrase);
|
||||
|
||||
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
|
||||
}
|
||||
@ -589,8 +540,11 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
private void saveInDatabase(Passphrase passphrase) {
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
|
||||
Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
|
||||
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
@ -602,6 +556,10 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
|
||||
// get returned data bundle
|
||||
@ -637,7 +595,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.View;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class EncryptActivity extends BaseActivity {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC = 0x00008002;
|
||||
|
||||
// For NFC data
|
||||
protected Passphrase mSigningKeyPassphrase = null;
|
||||
protected Date mNfcTimestamp = null;
|
||||
protected byte[] mNfcHash = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected void startPassphraseDialog(long subkeyId) {
|
||||
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
}
|
||||
|
||||
protected void startNfcSign(long keyId, Passphrase pin, byte[] hashToSign, int hashAlgo) {
|
||||
// build PendingIntent for Yubikey NFC operations
|
||||
Intent intent = new Intent(this, NfcActivity.class);
|
||||
intent.setAction(NfcActivity.ACTION_SIGN_HASH);
|
||||
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
|
||||
intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(NfcActivity.EXTRA_PIN, pin);
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
|
||||
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
|
||||
|
||||
startActivityForResult(intent, REQUEST_CODE_NFC);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
mSigningKeyPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
|
||||
startEncrypt();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
mNfcHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
startEncrypt();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startEncrypt() {
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
return;
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle());
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
this,
|
||||
getString(R.string.progress_encrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
|
||||
PgpSignEncryptResult pgpResult = result.getPending();
|
||||
|
||||
if (pgpResult != null && pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
|
||||
mNfcTimestamp = pgpResult.getNfcTimestamp();
|
||||
startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(),
|
||||
pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
result.createNotify(EncryptActivity.this).show();
|
||||
}
|
||||
|
||||
// no matter the result, reset parameters
|
||||
mSigningKeyPassphrase = null;
|
||||
mNfcHash = null;
|
||||
mNfcTimestamp = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
protected abstract boolean inputIsValid();
|
||||
|
||||
protected abstract void onEncryptSuccess(SignEncryptResult result);
|
||||
|
||||
protected abstract SignEncryptParcel createEncryptBundle();
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface EncryptActivityInterface {
|
||||
|
||||
public interface UpdateListener {
|
||||
void onNotifyUpdate();
|
||||
}
|
||||
|
||||
public boolean isUseArmor();
|
||||
public boolean isUseCompression();
|
||||
public boolean isEncryptFilenames();
|
||||
public boolean isHiddenRecipients();
|
||||
|
||||
public long getSignatureKey();
|
||||
public long[] getEncryptionKeys();
|
||||
public String[] getEncryptionUsers();
|
||||
public void setSignatureKey(long signatureKey);
|
||||
public void setEncryptionKeys(long[] encryptionKeys);
|
||||
public void setEncryptionUsers(String[] encryptionUsers);
|
||||
|
||||
public void setPassphrase(Passphrase passphrase);
|
||||
|
||||
// ArrayList on purpose as only those are parcelable
|
||||
public ArrayList<Uri> getInputUris();
|
||||
public ArrayList<Uri> getOutputUris();
|
||||
public void setInputUris(ArrayList<Uri> uris);
|
||||
public void setOutputUris(ArrayList<Uri> uris);
|
||||
|
||||
public String getMessage();
|
||||
public void setMessage(String message);
|
||||
|
||||
/**
|
||||
* Call this to notify the UI for changes done on the array lists or arrays,
|
||||
* automatically called if setter is used
|
||||
*/
|
||||
public void notifyUpdate();
|
||||
|
||||
public void startEncrypt(boolean share);
|
||||
}
|
@ -37,10 +37,6 @@ import java.util.regex.Matcher;
|
||||
|
||||
public class EncryptDecryptOverviewFragment extends Fragment {
|
||||
|
||||
View mEncryptFile;
|
||||
View mEncryptText;
|
||||
View mDecryptFile;
|
||||
View mDecryptFromClipboard;
|
||||
View mClipboardIcon;
|
||||
|
||||
@Override
|
||||
@ -53,10 +49,10 @@ public class EncryptDecryptOverviewFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false);
|
||||
|
||||
mEncryptFile = view.findViewById(R.id.encrypt_files);
|
||||
mEncryptText = view.findViewById(R.id.encrypt_text);
|
||||
mDecryptFile = view.findViewById(R.id.decrypt_files);
|
||||
mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
|
||||
View mEncryptFile = view.findViewById(R.id.encrypt_files);
|
||||
View mEncryptText = view.findViewById(R.id.encrypt_text);
|
||||
View mDecryptFile = view.findViewById(R.id.decrypt_files);
|
||||
View mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
|
||||
mClipboardIcon = view.findViewById(R.id.clipboard_icon);
|
||||
|
||||
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -18,32 +18,25 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptFilesActivity extends EncryptActivity implements EncryptActivityInterface {
|
||||
public class EncryptFilesActivity extends BaseActivity implements
|
||||
EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
|
||||
EncryptFilesFragment.IMode {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA;
|
||||
@ -55,302 +48,22 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
|
||||
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS";
|
||||
|
||||
// view
|
||||
private int mCurrentMode = MODE_ASYMMETRIC;
|
||||
|
||||
// tabs
|
||||
private static final int MODE_ASYMMETRIC = 0;
|
||||
private static final int MODE_SYMMETRIC = 1;
|
||||
|
||||
// model used by fragments
|
||||
private boolean mUseArmor = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mDeleteAfterEncrypt = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mEncryptFilenames = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mPassphrase = new Passphrase();
|
||||
|
||||
private ArrayList<Uri> mInputUris;
|
||||
private ArrayList<Uri> mOutputUris;
|
||||
private String mMessage = "";
|
||||
|
||||
public boolean isModeSymmetric() {
|
||||
return MODE_SYMMETRIC == mCurrentMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseArmor() {
|
||||
return mUseArmor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseCompression() {
|
||||
return mUseCompression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncryptFilenames() {
|
||||
return mEncryptFilenames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHiddenRecipients() {
|
||||
return mHiddenRecipients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSignatureKey() {
|
||||
return mSigningKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getEncryptionKeys() {
|
||||
return mEncryptionKeyIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEncryptionUsers() {
|
||||
return mEncryptionUserIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignatureKey(long signatureKey) {
|
||||
mSigningKeyId = signatureKey;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionKeys(long[] encryptionKeys) {
|
||||
mEncryptionKeyIds = encryptionKeys;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionUsers(String[] encryptionUsers) {
|
||||
mEncryptionUserIds = encryptionUsers;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassphrase(Passphrase passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputUris(ArrayList<Uri> uris) {
|
||||
mInputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputUris(ArrayList<Uri> uris) {
|
||||
mOutputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUpdate() {
|
||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
||||
if (fragment instanceof EncryptActivityInterface.UpdateListener) {
|
||||
((UpdateListener) fragment).onNotifyUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
startEncrypt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptSuccess(final SignEncryptResult result) {
|
||||
if (mDeleteAfterEncrypt) {
|
||||
final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]);
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris);
|
||||
deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
|
||||
|
||||
@Override
|
||||
public void onDeleted() {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(EncryptFilesActivity.this).show();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
|
||||
mInputUris.clear();
|
||||
notifyUpdate();
|
||||
} else {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(EncryptFilesActivity.this).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.addInputUris(mInputUris);
|
||||
data.addOutputUris(mOutputUris);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setEnableAsciiArmorOutput(mUseArmor);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
data.setSignaturePassphrase(mSigningKeyPassphrase);
|
||||
data.setNfcState(mNfcHash, mNfcTimestamp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt() {
|
||||
Intent prototype = createSendIntent();
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent() {
|
||||
Intent sendIntent;
|
||||
// file
|
||||
if (mOutputUris.size() == 1) {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
|
||||
} else {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
|
||||
}
|
||||
sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
|
||||
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
// file checks
|
||||
|
||||
if (mInputUris.isEmpty()) {
|
||||
Notify.create(this, R.string.no_file_selected, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
} else if (mInputUris.size() > 1 && !mShareAfterEncrypt) {
|
||||
// This should be impossible...
|
||||
return false;
|
||||
} else if (mInputUris.size() != mOutputUris.size()) {
|
||||
// This as well
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
// Files must be encrypted, only text can be signed-only right now
|
||||
if (!gotEncryptionKeys) {
|
||||
Notify.create(this, R.string.select_encryption_key, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Fragment mModeFragment;
|
||||
EncryptFilesFragment mEncryptFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(getIntent());
|
||||
updateModeFragment();
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -358,73 +71,10 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
setContentView(R.layout.encrypt_files_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.encrypt_file_activity, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric: {
|
||||
mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC;
|
||||
updateModeFragment();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_use_armor: {
|
||||
mUseArmor = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_delete_after_encrypt: {
|
||||
mDeleteAfterEncrypt = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_encrypt_filenames: {
|
||||
mEncryptFilenames = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateModeFragment() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_pager_mode,
|
||||
mCurrentMode == MODE_SYMMETRIC
|
||||
? new EncryptSymmetricFragment()
|
||||
: new EncryptAsymmetricFragment()
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
private void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
@ -453,14 +103,56 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
}
|
||||
|
||||
mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
|
||||
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
|
||||
|
||||
// preselect keys given by intent
|
||||
mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
if (savedInstanceState == null) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
// Save uris
|
||||
mInputUris = uris;
|
||||
mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
|
||||
transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
|
||||
|
||||
mEncryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
|
||||
transaction.replace(R.id.encrypt_file_container, mEncryptFragment, "files");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModeChanged(boolean symmetric) {
|
||||
// switch fragments
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_mode_container,
|
||||
symmetric
|
||||
? EncryptModeSymmetricFragment.newInstance()
|
||||
: EncryptModeAsymmetricFragment.newInstance(0, null)
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignatureKeyIdChanged(long signatureKeyId) {
|
||||
mEncryptFragment.setSigningKeyId(signatureKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
|
||||
mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
|
||||
mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassphraseChanged(Passphrase passphrase) {
|
||||
mEncryptFragment.setPassphrase(passphrase);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.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
|
||||
@ -17,57 +17,130 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v7.widget.DefaultItemAnimator;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptFilesFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public class EncryptFilesFragment extends CryptoOperationFragment {
|
||||
|
||||
public interface IMode {
|
||||
public void onModeChanged(boolean symmetric);
|
||||
}
|
||||
|
||||
public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor";
|
||||
public static final String ARG_URIS = "uris";
|
||||
|
||||
private static final int REQUEST_CODE_INPUT = 0x00007003;
|
||||
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
private IMode mModeInterface;
|
||||
|
||||
// view
|
||||
private View mAddView;
|
||||
private ListView mSelectedFiles;
|
||||
private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter();
|
||||
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>();
|
||||
private boolean mSymmetricMode = false;
|
||||
private boolean mUseArmor = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mDeleteAfterEncrypt = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mEncryptFilenames = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mPassphrase = new Passphrase();
|
||||
|
||||
private ArrayList<Uri> mOutputUris = new ArrayList<>();
|
||||
|
||||
private RecyclerView mSelectedFiles;
|
||||
|
||||
ArrayList<FilesAdapter.ViewModel> mFilesModels;
|
||||
FilesAdapter mFilesAdapter;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) {
|
||||
EncryptFilesFragment frag = new EncryptFilesFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor);
|
||||
args.putParcelableArrayList(ARG_URIS, uris);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
public void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
}
|
||||
|
||||
public void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptionUserIds = encryptionUserIds;
|
||||
}
|
||||
|
||||
public void setSigningKeyId(long signingKeyId) {
|
||||
mSigningKeyId = signingKeyId;
|
||||
}
|
||||
|
||||
public void setPassphrase(Passphrase passphrase) {
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mModeInterface = (IMode) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity + " must be IMode");
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,17 +150,28 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_files_fragment, container, false);
|
||||
mSelectedFiles = (RecyclerView) view.findViewById(R.id.selected_files_list);
|
||||
|
||||
mAddView = inflater.inflate(R.layout.file_list_entry_add, null);
|
||||
mAddView.setOnClickListener(new View.OnClickListener() {
|
||||
mSelectedFiles.addItemDecoration(new SpacesItemDecoration(
|
||||
FormattingUtils.dpToPx(getActivity(), 4)));
|
||||
mSelectedFiles.setHasFixedSize(true);
|
||||
mSelectedFiles.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
mSelectedFiles.setItemAnimator(new DefaultItemAnimator());
|
||||
|
||||
mFilesModels = new ArrayList<>();
|
||||
mFilesAdapter = new FilesAdapter(getActivity(), mFilesModels, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
addInputUri();
|
||||
}
|
||||
});
|
||||
mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list);
|
||||
mSelectedFiles.addFooterView(mAddView);
|
||||
mSelectedFiles.setAdapter(mAdapter);
|
||||
|
||||
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_URIS);
|
||||
if (inputUris != null) {
|
||||
mFilesAdapter.addAll(inputUris);
|
||||
}
|
||||
mUseArmor = getArguments().getBoolean(ARG_USE_ASCII_ARMOR);
|
||||
mSelectedFiles.setAdapter(mFilesAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
@ -95,7 +179,6 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@ -103,8 +186,8 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptFilesFragment.this, mEncryptInterface.getInputUris().isEmpty() ?
|
||||
null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1),
|
||||
FileHelper.openFile(EncryptFilesFragment.this, mFilesModels.isEmpty() ?
|
||||
null : mFilesModels.get(mFilesModels.size() - 1).inputUri,
|
||||
"*/*", REQUEST_CODE_INPUT);
|
||||
}
|
||||
}
|
||||
@ -114,34 +197,27 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncryptInterface.getInputUris().contains(inputUri)) {
|
||||
try {
|
||||
mFilesAdapter.add(inputUri);
|
||||
} catch (IOException e) {
|
||||
Notify.create(getActivity(),
|
||||
getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)),
|
||||
Notify.Style.ERROR).show(this);
|
||||
Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
mEncryptInterface.getInputUris().add(inputUri);
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mSelectedFiles.requestFocus();
|
||||
}
|
||||
|
||||
private void delInputUri(int position) {
|
||||
mEncryptInterface.getInputUris().remove(position);
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mSelectedFiles.requestFocus();
|
||||
}
|
||||
|
||||
private void showOutputFileDialog() {
|
||||
if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) {
|
||||
if (mFilesModels.size() > 1 || mFilesModels.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
Uri inputUri = mEncryptInterface.getInputUris().get(0);
|
||||
FilesAdapter.ViewModel model = mFilesModels.get(0);
|
||||
String targetName =
|
||||
(mEncryptInterface.isEncryptFilenames() ? "1" : FileHelper.getFilename(getActivity(), inputUri))
|
||||
+ (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(inputUri.getPath());
|
||||
File file = new File(model.inputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
File targetFile = new File(parentDir, targetName);
|
||||
FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file),
|
||||
@ -152,44 +228,61 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
}
|
||||
|
||||
private void encryptClicked(boolean share) {
|
||||
if (mEncryptInterface.getInputUris().isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this);
|
||||
if (mFilesModels.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.error_no_file_selected,
|
||||
Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
if (share) {
|
||||
mEncryptInterface.getOutputUris().clear();
|
||||
mOutputUris.clear();
|
||||
int filenameCounter = 1;
|
||||
for (Uri uri : mEncryptInterface.getInputUris()) {
|
||||
for (FilesAdapter.ViewModel model : mFilesModels) {
|
||||
String targetName =
|
||||
(mEncryptInterface.isEncryptFilenames() ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), uri))
|
||||
+ (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||
(mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||
filenameCounter++;
|
||||
}
|
||||
mEncryptInterface.startEncrypt(true);
|
||||
startEncrypt(true);
|
||||
} else {
|
||||
if (mEncryptInterface.getInputUris().size() > 1) {
|
||||
Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show(this);
|
||||
if (mFilesModels.size() > 1) {
|
||||
Notify.create(getActivity(), R.string.error_multi_not_supported,
|
||||
Notify.Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
showOutputFileDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public boolean handleClipData(Intent data) {
|
||||
if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
|
||||
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
|
||||
Uri uri = data.getClipData().getItemAt(i).getUri();
|
||||
if (uri != null) addInputUri(uri);
|
||||
public void addFile(Intent data) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
addInputUri(data.getData());
|
||||
} else {
|
||||
if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
|
||||
for (int i = 0; i < data.getClipData().getItemCount(); i++) {
|
||||
Uri uri = data.getClipData().getItemAt(i).getUri();
|
||||
if (uri != null) {
|
||||
addInputUri(uri);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// fallback, try old method to get single uri
|
||||
addInputUri(data.getData());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.encrypt_file_fragment, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.encrypt_save: {
|
||||
encryptClicked(false);
|
||||
@ -199,6 +292,32 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
encryptClicked(true);
|
||||
break;
|
||||
}
|
||||
case R.id.check_use_symmetric: {
|
||||
mSymmetricMode = item.isChecked();
|
||||
mModeInterface.onModeChanged(mSymmetricMode);
|
||||
break;
|
||||
}
|
||||
case R.id.check_use_armor: {
|
||||
mUseArmor = item.isChecked();
|
||||
break;
|
||||
}
|
||||
case R.id.check_delete_after_encrypt: {
|
||||
mDeleteAfterEncrypt = item.isChecked();
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
break;
|
||||
}
|
||||
case R.id.check_encrypt_filenames: {
|
||||
mEncryptFilenames = item.isChecked();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@ -206,24 +325,234 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
// file checks
|
||||
|
||||
if (mFilesModels.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
|
||||
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
|
||||
// This should be impossible...
|
||||
return false;
|
||||
} else if (mFilesModels.size() != mOutputUris.size()) {
|
||||
Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
|
||||
// This as well
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSymmetricMode) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
// Files must be encrypted, only text can be signed-only right now
|
||||
if (!gotEncryptionKeys) {
|
||||
Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onEncryptSuccess(final SignEncryptResult result) {
|
||||
if (mDeleteAfterEncrypt) {
|
||||
DeleteFileDialogFragment deleteFileDialog =
|
||||
DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
|
||||
deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
|
||||
|
||||
@Override
|
||||
public void onDeleted() {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
} else {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.addInputUris(mFilesAdapter.getAsArrayList());
|
||||
data.addOutputUris(mOutputUris);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setEnableAsciiArmorOutput(mUseArmor);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
if (mSymmetricMode) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt() {
|
||||
Intent prototype = createSendIntent();
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent() {
|
||||
Intent sendIntent;
|
||||
// file
|
||||
if (mOutputUris.size() == 1) {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
|
||||
} else {
|
||||
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
|
||||
}
|
||||
sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
|
||||
|
||||
if (!mSymmetricMode && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
Log.d(Constants.TAG, "Input not valid!");
|
||||
return;
|
||||
}
|
||||
Log.d(Constants.TAG, "Input valid!");
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
|
||||
final SignEncryptParcel input = createEncryptBundle();
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_encrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
true,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
// handle pending messages
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_INPUT: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !handleClipData(data)) {
|
||||
addInputUri(data.getData());
|
||||
}
|
||||
addFile(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case REQUEST_CODE_OUTPUT: {
|
||||
// This happens after output file was selected, so start our operation
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
mEncryptInterface.getOutputUris().clear();
|
||||
mEncryptInterface.getOutputUris().add(data.getData());
|
||||
mEncryptInterface.notifyUpdate();
|
||||
mEncryptInterface.startEncrypt(false);
|
||||
mOutputUris.clear();
|
||||
mOutputUris.add(data.getData());
|
||||
startEncrypt(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -236,67 +565,190 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
// Clear cache if needed
|
||||
for (Uri uri : new HashSet<>(thumbnailCache.keySet())) {
|
||||
if (!mEncryptInterface.getInputUris().contains(uri)) {
|
||||
thumbnailCache.remove(uri);
|
||||
public static class FilesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private Activity mActivity;
|
||||
private List<ViewModel> mDataset;
|
||||
private View.OnClickListener mFooterOnClickListener;
|
||||
private static final int TYPE_FOOTER = 0;
|
||||
private static final int TYPE_ITEM = 1;
|
||||
|
||||
public static class ViewModel {
|
||||
Uri inputUri;
|
||||
Bitmap thumbnail;
|
||||
String filename;
|
||||
long fileSize;
|
||||
|
||||
ViewModel(Context context, Uri inputUri) {
|
||||
this.inputUri = inputUri;
|
||||
int px = FormattingUtils.dpToPx(context, 48);
|
||||
this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px));
|
||||
this.filename = FileHelper.getFilename(context, inputUri);
|
||||
this.fileSize = FileHelper.getFileSize(context, inputUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Depends on inputUri only
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ViewModel viewModel = (ViewModel) o;
|
||||
return !(inputUri != null ? !inputUri.equals(viewModel.inputUri)
|
||||
: viewModel.inputUri != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Depends on inputUri only
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return inputUri != null ? inputUri.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return inputUri.toString();
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
// Provide a reference to the views for each data item
|
||||
// Complex data items may need more than one view per item, and
|
||||
// you provide access to all the views for a data item in a view holder
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public TextView filename;
|
||||
public TextView fileSize;
|
||||
public View removeButton;
|
||||
public ImageView thumbnail;
|
||||
|
||||
private class SelectedFilesAdapter extends BaseAdapter {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mEncryptInterface.getInputUris().size();
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
filename = (TextView) itemView.findViewById(R.id.filename);
|
||||
fileSize = (TextView) itemView.findViewById(R.id.filesize);
|
||||
removeButton = itemView.findViewById(R.id.action_remove_file_from_list);
|
||||
thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mEncryptInterface.getInputUris().get(position);
|
||||
class FooterHolder extends RecyclerView.ViewHolder {
|
||||
public Button mAddButton;
|
||||
|
||||
public FooterHolder(View itemView) {
|
||||
super(itemView);
|
||||
mAddButton = (Button) itemView.findViewById(R.id.file_list_entry_add);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).hashCode();
|
||||
// Provide a suitable constructor (depends on the kind of dataset)
|
||||
public FilesAdapter(Activity activity, List<ViewModel> myDataset, View.OnClickListener onFooterClickListener) {
|
||||
mActivity = activity;
|
||||
mDataset = myDataset;
|
||||
mFooterOnClickListener = onFooterClickListener;
|
||||
}
|
||||
|
||||
// Create new views (invoked by the layout manager)
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
Uri inputUri = mEncryptInterface.getInputUris().get(position);
|
||||
View view;
|
||||
if (convertView == null) {
|
||||
view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null);
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (viewType == TYPE_FOOTER) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.file_list_entry_add, parent, false);
|
||||
return new FooterHolder(v);
|
||||
} else {
|
||||
view = convertView;
|
||||
//inflate your layout and pass it to view holder
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.file_list_entry, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
((TextView) view.findViewById(R.id.filename)).setText(FileHelper.getFilename(getActivity(), inputUri));
|
||||
long size = FileHelper.getFileSize(getActivity(), inputUri);
|
||||
if (size == -1) {
|
||||
((TextView) view.findViewById(R.id.filesize)).setText("");
|
||||
} else {
|
||||
((TextView) view.findViewById(R.id.filesize)).setText(FileHelper.readableFileSize(size));
|
||||
}
|
||||
view.findViewById(R.id.action_remove_file_from_list).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
delInputUri(position);
|
||||
}
|
||||
|
||||
// Replace the contents of a view (invoked by the layout manager)
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||
if (holder instanceof FooterHolder) {
|
||||
FooterHolder thisHolder = (FooterHolder) holder;
|
||||
thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
|
||||
} else if (holder instanceof ViewHolder) {
|
||||
ViewHolder thisHolder = (ViewHolder) holder;
|
||||
// - get element from your dataset at this position
|
||||
// - replace the contents of the view with that element
|
||||
final ViewModel model = mDataset.get(position);
|
||||
|
||||
thisHolder.filename.setText(model.filename);
|
||||
if (model.fileSize == -1) {
|
||||
thisHolder.fileSize.setText("");
|
||||
} else {
|
||||
thisHolder.fileSize.setText(FileHelper.readableFileSize(model.fileSize));
|
||||
}
|
||||
thisHolder.removeButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
remove(model);
|
||||
}
|
||||
});
|
||||
if (model.thumbnail != null) {
|
||||
thisHolder.thumbnail.setImageBitmap(model.thumbnail);
|
||||
} else {
|
||||
thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am);
|
||||
}
|
||||
});
|
||||
int px = FormattingUtils.dpToPx(getActivity(), 48);
|
||||
if (!thumbnailCache.containsKey(inputUri)) {
|
||||
thumbnailCache.put(inputUri, FileHelper.getThumbnail(getActivity(), inputUri, new Point(px, px)));
|
||||
}
|
||||
Bitmap bitmap = thumbnailCache.get(inputUri);
|
||||
if (bitmap != null) {
|
||||
((ImageView) view.findViewById(R.id.thumbnail)).setImageBitmap(bitmap);
|
||||
} else {
|
||||
((ImageView) view.findViewById(R.id.thumbnail)).setImageResource(R.drawable.ic_doc_generic_am);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
// Return the size of your dataset (invoked by the layout manager)
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mDataset.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (isPositionFooter(position)) {
|
||||
return TYPE_FOOTER;
|
||||
} else {
|
||||
return TYPE_ITEM;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPositionFooter(int position) {
|
||||
return position == mDataset.size();
|
||||
}
|
||||
|
||||
public void add(Uri inputUri) throws IOException {
|
||||
ViewModel newModel = new ViewModel(mActivity, inputUri);
|
||||
if (mDataset.contains(newModel)) {
|
||||
throw new IOException("Already added!");
|
||||
}
|
||||
mDataset.add(newModel);
|
||||
notifyItemInserted(mDataset.size() - 1);
|
||||
}
|
||||
|
||||
public void addAll(ArrayList<Uri> inputUris) {
|
||||
if (inputUris != null) {
|
||||
int startIndex = mDataset.size();
|
||||
for (Uri inputUri : inputUris) {
|
||||
ViewModel newModel = new ViewModel(mActivity, inputUri);
|
||||
if (mDataset.contains(newModel)) {
|
||||
Log.e(Constants.TAG, "Skipped duplicate " + inputUri.toString());
|
||||
} else {
|
||||
mDataset.add(newModel);
|
||||
}
|
||||
}
|
||||
notifyItemRangeInserted(startIndex, mDataset.size() - startIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(ViewModel model) {
|
||||
int position = mDataset.indexOf(model);
|
||||
mDataset.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public ArrayList<Uri> getAsArrayList() {
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
for (ViewModel model : mDataset) {
|
||||
uris.add(model.inputUri);
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.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
|
||||
@ -44,7 +44,17 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public class EncryptModeAsymmetricFragment extends Fragment {
|
||||
|
||||
public interface IAsymmetric {
|
||||
|
||||
public void onSignatureKeyIdChanged(long signatureKeyId);
|
||||
|
||||
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds);
|
||||
|
||||
public void onEncryptionUserIdsChanged(String[] encryptionUserIds);
|
||||
}
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
// view
|
||||
@ -52,37 +62,43 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
private EncryptKeyCompletionView mEncryptKeyView;
|
||||
|
||||
// model
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
private IAsymmetric mEncryptInterface;
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
if (mSign != null) {
|
||||
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
}
|
||||
// @Override
|
||||
// public void updateUi() {
|
||||
// if (mSign != null) {
|
||||
// mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
// }
|
||||
// }
|
||||
|
||||
public static final String ARG_SINGATURE_KEY_ID = "signature_key_id";
|
||||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) {
|
||||
EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_SINGATURE_KEY_ID, signatureKey);
|
||||
args.putLongArray(ARG_ENCRYPTION_KEY_IDS, encryptionKeyIds);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mEncryptInterface = (IAsymmetric) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity + " must implement IAsymmetric");
|
||||
}
|
||||
}
|
||||
|
||||
private void setSignatureKeyId(long signatureKeyId) {
|
||||
mEncryptInterface.setSignatureKey(signatureKeyId);
|
||||
}
|
||||
|
||||
private void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptInterface.setEncryptionKeys(encryptionKeyIds);
|
||||
}
|
||||
|
||||
private void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptInterface.setEncryptionUsers(encryptionUserIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@ -94,7 +110,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
|
||||
@Override
|
||||
public void onKeyChanged(long masterKeyId) {
|
||||
setSignatureKeyId(masterKeyId);
|
||||
mEncryptInterface.onSignatureKeyIdChanged(masterKeyId);
|
||||
}
|
||||
});
|
||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||
@ -109,7 +125,9 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
mProviderHelper = new ProviderHelper(getActivity());
|
||||
|
||||
// preselect keys given
|
||||
preselectKeys();
|
||||
long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
|
||||
long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
|
||||
preselectKeys(signatureKeyId, encryptionKeyIds);
|
||||
|
||||
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
|
||||
@Override
|
||||
@ -131,24 +149,20 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
/**
|
||||
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
|
||||
*/
|
||||
private void preselectKeys() {
|
||||
// TODO all of this works under the assumption that the first suitable subkey is always used!
|
||||
// not sure if we need to distinguish between different subkeys here?
|
||||
long signatureKey = mEncryptInterface.getSignatureKey();
|
||||
if (signatureKey != Constants.key.none) {
|
||||
private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
|
||||
if (signatureKeyId != Constants.key.none) {
|
||||
try {
|
||||
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(signatureKey));
|
||||
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
|
||||
if (keyring.hasAnySecret()) {
|
||||
setSignatureKeyId(keyring.getMasterKeyId());
|
||||
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
|
||||
mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId());
|
||||
mSign.setSelectedKeyId(signatureKeyId);
|
||||
}
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
long[] encryptionKeyIds = mEncryptInterface.getEncryptionKeys();
|
||||
if (encryptionKeyIds != null) {
|
||||
for (long preselectedId : encryptionKeyIds) {
|
||||
try {
|
||||
@ -181,7 +195,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
for (int i = 0; i < keyIds.size(); i++) {
|
||||
keyIdsArr[i] = iterator.next();
|
||||
}
|
||||
setEncryptionKeyIds(keyIdsArr);
|
||||
setEncryptionUserIds(userIds.toArray(new String[userIds.size()]));
|
||||
mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr);
|
||||
mEncryptInterface.onEncryptionUserIdsChanged(userIds.toArray(new String[userIds.size()]));
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.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
|
||||
@ -30,20 +30,37 @@ import android.widget.EditText;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
public class EncryptSymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
|
||||
public class EncryptModeSymmetricFragment extends Fragment {
|
||||
|
||||
EncryptActivityInterface mEncryptInterface;
|
||||
public interface ISymmetric {
|
||||
|
||||
public void onPassphraseChanged(Passphrase passphrase);
|
||||
}
|
||||
|
||||
private ISymmetric mEncryptInterface;
|
||||
|
||||
private EditText mPassphrase;
|
||||
private EditText mPassphraseAgain;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptModeSymmetricFragment newInstance() {
|
||||
EncryptModeSymmetricFragment frag = new EncryptModeSymmetricFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mEncryptInterface = (ISymmetric) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity.toString() + " must implement ISymmetric");
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,9 +91,9 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
|
||||
p1.removeFromMemory();
|
||||
p2.removeFromMemory();
|
||||
if (passesEquals) {
|
||||
mEncryptInterface.setPassphrase(new Passphrase(mPassphrase.getText()));
|
||||
mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText()));
|
||||
} else {
|
||||
mEncryptInterface.setPassphrase(null);
|
||||
mEncryptInterface.onPassphraseChanged(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -86,8 +103,4 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -19,31 +19,21 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptTextActivity extends EncryptActivity implements EncryptActivityInterface {
|
||||
public class EncryptTextActivity extends BaseActivity implements
|
||||
EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
|
||||
EncryptTextFragment.IMode {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT;
|
||||
@ -55,285 +45,22 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
|
||||
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
|
||||
|
||||
// view
|
||||
private int mCurrentMode = MODE_ASYMMETRIC;
|
||||
|
||||
// tabs
|
||||
private static final int MODE_ASYMMETRIC = 0;
|
||||
private static final int MODE_SYMMETRIC = 1;
|
||||
|
||||
// model used by fragments
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
// TODO Constants.key.none? What's wrong with a null value?
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mPassphrase = new Passphrase();
|
||||
|
||||
private ArrayList<Uri> mInputUris;
|
||||
private ArrayList<Uri> mOutputUris;
|
||||
private String mMessage = "";
|
||||
|
||||
public boolean isModeSymmetric() {
|
||||
return MODE_SYMMETRIC == mCurrentMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseArmor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncryptFilenames() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseCompression() {
|
||||
return mUseCompression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHiddenRecipients() {
|
||||
return mHiddenRecipients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSignatureKey() {
|
||||
return mSigningKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getEncryptionKeys() {
|
||||
return mEncryptionKeyIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEncryptionUsers() {
|
||||
return mEncryptionUserIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSignatureKey(long signatureKey) {
|
||||
mSigningKeyId = signatureKey;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionKeys(long[] encryptionKeys) {
|
||||
mEncryptionKeyIds = encryptionKeys;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncryptionUsers(String[] encryptionUsers) {
|
||||
mEncryptionUserIds = encryptionUsers;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassphrase(Passphrase passphrase) {
|
||||
if (mPassphrase != null) {
|
||||
mPassphrase.removeFromMemory();
|
||||
}
|
||||
mPassphrase = passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputUris(ArrayList<Uri> uris) {
|
||||
mInputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputUris(ArrayList<Uri> uris) {
|
||||
mOutputUris = uris;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyUpdate() {
|
||||
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
|
||||
if (fragment instanceof UpdateListener) {
|
||||
((UpdateListener) fragment).onNotifyUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
startEncrypt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEncryptSuccess(SignEncryptResult result) {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||
} else {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(result.getResultBytes());
|
||||
result.createNotify(EncryptTextActivity.this).show();
|
||||
// Notify.create(EncryptTextActivity.this,
|
||||
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
|
||||
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.setBytes(mMessage.getBytes());
|
||||
data.setCleartextSignature(true);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
// Always use armor for messages
|
||||
data.setEnableAsciiArmorOutput(true);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
data.setSignaturePassphrase(mSigningKeyPassphrase);
|
||||
data.setNfcState(mNfcHash, mNfcTimestamp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void copyToClipboard(byte[] resultBytes) {
|
||||
ClipboardReflection.copyToClipboard(this, new String(resultBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
|
||||
Intent prototype = createSendIntent(resultBytes);
|
||||
String title = getString(R.string.title_share_message);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(byte[] resultBytes) {
|
||||
Intent sendIntent;
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
|
||||
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
// pass trough email addresses as extra for email applications
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
if (mMessage == null) {
|
||||
Notify.create(this, R.string.error_message, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mPassphrase == null) {
|
||||
Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
if (mPassphrase.isEmpty()) {
|
||||
Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mSigningKeyId == 0) {
|
||||
Notify.create(this, R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||
.show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Fragment mModeFragment;
|
||||
EncryptTextFragment mEncryptFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// if called with an intent action, do not init drawer navigation
|
||||
if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) {
|
||||
// lock drawer
|
||||
// deactivateDrawerNavigation();
|
||||
// TODO: back button to key?
|
||||
} else {
|
||||
// activateDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
}, false);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(getIntent());
|
||||
updateModeFragment();
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -341,58 +68,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
setContentView(R.layout.encrypt_text_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.encrypt_text_activity, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private void updateModeFragment() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_pager_mode,
|
||||
mCurrentMode == MODE_SYMMETRIC
|
||||
? new EncryptSymmetricFragment()
|
||||
: new EncryptAsymmetricFragment()
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric: {
|
||||
mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC;
|
||||
updateModeFragment();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
notifyUpdate();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
private void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
@ -423,19 +105,61 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
}
|
||||
|
||||
String textData = extras.getString(EXTRA_TEXT);
|
||||
if (ACTION_ENCRYPT_TEXT.equals(action) && textData == null) {
|
||||
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
|
||||
return;
|
||||
}
|
||||
|
||||
// preselect keys given by intent
|
||||
mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
|
||||
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
|
||||
|
||||
/**
|
||||
* Main Actions
|
||||
*/
|
||||
if (ACTION_ENCRYPT_TEXT.equals(action) && textData != null) {
|
||||
mMessage = textData;
|
||||
} else if (ACTION_ENCRYPT_TEXT.equals(action)) {
|
||||
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
|
||||
transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
|
||||
|
||||
mEncryptFragment = EncryptTextFragment.newInstance(textData);
|
||||
transaction.replace(R.id.encrypt_text_container, mEncryptFragment, "text");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModeChanged(boolean symmetric) {
|
||||
// switch fragments
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.encrypt_mode_container,
|
||||
symmetric
|
||||
? EncryptModeSymmetricFragment.newInstance()
|
||||
: EncryptModeAsymmetricFragment.newInstance(0, null)
|
||||
)
|
||||
.commitAllowingStateLoss();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSignatureKeyIdChanged(long signatureKeyId) {
|
||||
mEncryptFragment.setSigningKeyId(signatureKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
|
||||
mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
|
||||
mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassphraseChanged(Passphrase passphrase) {
|
||||
mEncryptFragment.setSymmetricPassphrase(passphrase);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.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
|
||||
@ -18,32 +18,102 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class EncryptTextFragment extends CryptoOperationFragment {
|
||||
|
||||
public interface IMode {
|
||||
public void onModeChanged(boolean symmetric);
|
||||
}
|
||||
|
||||
public class EncryptTextFragment extends Fragment {
|
||||
public static final String ARG_TEXT = "text";
|
||||
|
||||
private IMode mModeInterface;
|
||||
|
||||
private boolean mSymmetricMode = false;
|
||||
private boolean mShareAfterEncrypt = false;
|
||||
private boolean mUseCompression = true;
|
||||
private boolean mHiddenRecipients = false;
|
||||
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
private String mEncryptionUserIds[] = null;
|
||||
// TODO Constants.key.none? What's wrong with a null value?
|
||||
private long mSigningKeyId = Constants.key.none;
|
||||
private Passphrase mSymmetricPassphrase = new Passphrase();
|
||||
private String mMessage = "";
|
||||
|
||||
private TextView mText;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
public void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
}
|
||||
|
||||
public void setEncryptionUserIds(String[] encryptionUserIds) {
|
||||
mEncryptionUserIds = encryptionUserIds;
|
||||
}
|
||||
|
||||
public void setSigningKeyId(long signingKeyId) {
|
||||
mSigningKeyId = signingKeyId;
|
||||
}
|
||||
|
||||
public void setSymmetricPassphrase(Passphrase passphrase) {
|
||||
mSymmetricPassphrase = passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static EncryptTextFragment newInstance(String text) {
|
||||
EncryptTextFragment frag = new EncryptTextFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_TEXT, text);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
mModeInterface = (IMode) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
throw new ClassCastException(activity.toString() + " must implement IMode");
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,39 +138,58 @@ public class EncryptTextFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
mEncryptInterface.setMessage(s.toString());
|
||||
mMessage = s.toString();
|
||||
}
|
||||
});
|
||||
|
||||
// set initial text
|
||||
if (mMessage != null) {
|
||||
mText.setText(mMessage);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mMessage = getArguments().getString(ARG_TEXT);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String text = mEncryptInterface.getMessage();
|
||||
if (text != null) {
|
||||
mText.setText(text);
|
||||
}
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.encrypt_text_fragment, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.isCheckable()) {
|
||||
item.setChecked(!item.isChecked());
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.check_use_symmetric: {
|
||||
mSymmetricMode = item.isChecked();
|
||||
mModeInterface.onModeChanged(mSymmetricMode);
|
||||
break;
|
||||
}
|
||||
case R.id.check_enable_compression: {
|
||||
mUseCompression = item.isChecked();
|
||||
break;
|
||||
}
|
||||
// case R.id.check_hidden_recipients: {
|
||||
// mHiddenRecipients = item.isChecked();
|
||||
// notifyUpdate();
|
||||
// break;
|
||||
// }
|
||||
case R.id.encrypt_copy: {
|
||||
mEncryptInterface.startEncrypt(false);
|
||||
startEncrypt(false);
|
||||
break;
|
||||
}
|
||||
case R.id.encrypt_share: {
|
||||
mEncryptInterface.startEncrypt(true);
|
||||
startEncrypt(true);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -109,4 +198,189 @@ public class EncryptTextFragment extends Fragment {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected void onEncryptSuccess(SignEncryptResult result) {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||
} else {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(result.getResultBytes());
|
||||
result.createNotify(getActivity()).show();
|
||||
// Notify.create(EncryptTextActivity.this,
|
||||
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
|
||||
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||
}
|
||||
}
|
||||
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.setBytes(mMessage.getBytes());
|
||||
data.setCleartextSignature(true);
|
||||
|
||||
if (mUseCompression) {
|
||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||
} else {
|
||||
data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
}
|
||||
data.setHiddenRecipients(mHiddenRecipients);
|
||||
data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
|
||||
|
||||
// Always use armor for messages
|
||||
data.setEnableAsciiArmorOutput(true);
|
||||
|
||||
if (mSymmetricMode) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
Passphrase passphrase = mSymmetricPassphrase;
|
||||
if (passphrase.isEmpty()) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void copyToClipboard(byte[] resultBytes) {
|
||||
ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
|
||||
Intent prototype = createSendIntent(resultBytes);
|
||||
String title = getString(R.string.title_share_message);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
|
||||
"org.thialfihar.android.apg.ui.EncryptActivity"
|
||||
};
|
||||
|
||||
return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(byte[] resultBytes) {
|
||||
Intent sendIntent;
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
|
||||
|
||||
if (!mSymmetricMode && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(user);
|
||||
if (userId.email != null) {
|
||||
users.add(userId.email);
|
||||
}
|
||||
}
|
||||
// pass trough email addresses as extra for email applications
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
protected boolean inputIsValid() {
|
||||
if (mMessage == null) {
|
||||
Notify.create(getActivity(), R.string.error_message, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mSymmetricMode) {
|
||||
// symmetric encryption checks
|
||||
|
||||
if (mSymmetricPassphrase == null) {
|
||||
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
if (mSymmetricPassphrase.isEmpty()) {
|
||||
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption checks
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptionKeyIds != null
|
||||
&& mEncryptionKeyIds.length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mSigningKeyId == 0) {
|
||||
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void startEncrypt(boolean share) {
|
||||
mShareAfterEncrypt = share;
|
||||
cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
if (!inputIsValid()) {
|
||||
// Notify was created by inputIsValid.
|
||||
return;
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
|
||||
final SignEncryptParcel input = createEncryptBundle();
|
||||
final Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
|
||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_encrypting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (handlePendingMessage(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(serviceHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
serviceHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import com.astuetz.PagerSlidingTabStrip;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
public class HelpActivity extends BaseActivity {
|
||||
public static final String EXTRA_SELECTED_TAB = "selected_tab";
|
||||
|
@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.service.CloudImportService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
@ -47,7 +48,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ImportKeysActivity extends BaseActivity {
|
||||
public class ImportKeysActivity extends BaseNfcActivity {
|
||||
|
||||
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT =
|
||||
|
@ -53,9 +53,11 @@ import java.util.List;
|
||||
|
||||
public class ImportKeysListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
|
||||
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
private static final String ARG_BYTES = "bytes";
|
||||
private static final String ARG_SERVER_QUERY = "query";
|
||||
public static final String ARG_SERVER_QUERY = "query";
|
||||
public static final String ARG_NON_INTERACTIVE = "non_interactive";
|
||||
|
||||
private Activity mActivity;
|
||||
private ImportKeysAdapter mAdapter;
|
||||
@ -66,6 +68,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
private static final int LOADER_ID_CLOUD = 1;
|
||||
|
||||
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
|
||||
private boolean mNonInteractive;
|
||||
|
||||
public LoaderState getLoaderState() {
|
||||
return mLoaderState;
|
||||
@ -118,16 +121,19 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) {
|
||||
return newInstance(bytes, dataUri, serverQuery, false);
|
||||
}
|
||||
|
||||
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri,
|
||||
String serverQuery, boolean nonInteractive) {
|
||||
ImportKeysListFragment frag = new ImportKeysListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_BYTES, bytes);
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putString(ARG_SERVER_QUERY, serverQuery);
|
||||
args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@ -173,9 +179,11 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
mAdapter = new ImportKeysAdapter(mActivity);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
byte[] bytes = getArguments().getByteArray(ARG_BYTES);
|
||||
String query = getArguments().getString(ARG_SERVER_QUERY);
|
||||
Bundle args = getArguments();
|
||||
Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null;
|
||||
byte[] bytes = args.containsKey(ARG_BYTES) ? args.getByteArray(ARG_BYTES) : null;
|
||||
String query = args.containsKey(ARG_SERVER_QUERY) ? args.getString(ARG_SERVER_QUERY) : null;
|
||||
mNonInteractive = args.containsKey(ARG_NON_INTERACTIVE) ? args.getBoolean(ARG_NON_INTERACTIVE) : false;
|
||||
|
||||
if (dataUri != null || bytes != null) {
|
||||
mLoaderState = new BytesLoaderState(bytes, dataUri);
|
||||
@ -203,6 +211,10 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
if (mNonInteractive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Select checkbox!
|
||||
// Update underlying data and notify adapter of change. The adapter will
|
||||
// update the view automatically.
|
||||
|
@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NfcAdapter;
|
||||
@ -83,25 +84,22 @@ public class ImportKeysProxyActivity extends FragmentActivity {
|
||||
|
||||
returnResult = false;
|
||||
processScannedContent(dataUri);
|
||||
} else if (ACTION_SCAN_IMPORT.equals(action)) {
|
||||
} else if (ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
|
||||
returnResult = false;
|
||||
IntentIntegrator integrator = new IntentIntegrator(this);
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
|
||||
.setPrompt(getString(R.string.import_qr_code_text))
|
||||
.setResultDisplayDuration(0)
|
||||
.initiateScan();
|
||||
.setResultDisplayDuration(0);
|
||||
integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
integrator.initiateScan();
|
||||
} else if (ACTION_SCAN_WITH_RESULT.equals(action)) {
|
||||
returnResult = true;
|
||||
IntentIntegrator integrator = new IntentIntegrator(this);
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
|
||||
.setPrompt(getString(R.string.import_qr_code_text))
|
||||
.setResultDisplayDuration(0)
|
||||
.initiateScan();
|
||||
} else if (ACTION_QR_CODE_API.equals(action)) {
|
||||
// scan using xzing's Barcode Scanner from outside OpenKeychain
|
||||
|
||||
returnResult = false;
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
.setResultDisplayDuration(0);
|
||||
integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
integrator.initiateScan();
|
||||
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
|
||||
// Check to see if the Activity started due to an Android Beam
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
|
@ -22,6 +22,8 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
|
||||
public class LogDisplayActivity extends BaseActivity {
|
||||
|
||||
|
@ -1,313 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
*
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public class NfcIntentActivity extends BaseActivity {
|
||||
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
private Intent mServiceIntent;
|
||||
|
||||
private static final int TIMEOUT = 100000;
|
||||
|
||||
private NfcAdapter mNfcAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcActivity.onCreate");
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
String action = intent.getAction();
|
||||
|
||||
Log.d(Constants.TAG, action);
|
||||
Log.d(Constants.TAG, intent.getDataString());
|
||||
|
||||
// TODO check fingerprint
|
||||
// mFingerprint = data.getByteArray(EXTRA_FINGERPRINT);
|
||||
|
||||
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
|
||||
Log.d(Constants.TAG, "Action not supported: " + action);
|
||||
finish();
|
||||
}
|
||||
|
||||
try {
|
||||
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
|
||||
// Connect to the detected tag, setting a couple of settings
|
||||
IsoDep isoDep = IsoDep.get(detectedTag);
|
||||
isoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
|
||||
isoDep.connect();
|
||||
|
||||
nfcGreet(isoDep);
|
||||
// nfcPin(isoDep, "yoloswag");
|
||||
nfcGetFingerprint(isoDep);
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException!", e);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity,
|
||||
* disables NFC Foreground Dispatch
|
||||
*/
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
Log.d(Constants.TAG, "NfcActivity.onPause");
|
||||
|
||||
// disableNfcForegroundDispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity will start interacting with the user,
|
||||
* enables NFC Foreground Dispatch
|
||||
*/
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(Constants.TAG, "NfcActivity.onResume");
|
||||
|
||||
// enableNfcForegroundDispatch();
|
||||
}
|
||||
|
||||
/** Handle NFC communication and return a result.
|
||||
*
|
||||
* This method is called by onNewIntent above upon discovery of an NFC tag.
|
||||
* It handles initialization and login to the application, subsequently
|
||||
* calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
|
||||
* finishes the activity with an appropiate result.
|
||||
*
|
||||
* On general communication, see also
|
||||
* http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
|
||||
*
|
||||
* References to pages are generally related to the OpenPGP Application
|
||||
* on ISO SmartCard Systems specification.
|
||||
*
|
||||
*/
|
||||
private void nfcGreet(IsoDep isoDep) throws IOException {
|
||||
|
||||
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||
// See specification, page 51
|
||||
String accepted = "9000";
|
||||
|
||||
// Command APDU (page 51) for SELECT FILE command (page 29)
|
||||
String opening =
|
||||
"00" // CLA
|
||||
+ "A4" // INS
|
||||
+ "04" // P1
|
||||
+ "00" // P2
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if (!card(isoDep, opening).equals(accepted)) { // activate connection
|
||||
toast("Opening Error!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void nfcPin(IsoDep isoDep, String pin) throws IOException {
|
||||
|
||||
String data = "00CA006E00";
|
||||
String fingerprint = card(isoDep, data);
|
||||
|
||||
// Command APDU for VERIFY command (page 32)
|
||||
String login =
|
||||
"00" // CLA
|
||||
+ "20" // INS
|
||||
+ "00" // P1
|
||||
+ "82" // P2 (PW1)
|
||||
+ String.format("%02x", pin.length()) // Lc
|
||||
+ Hex.toHexString(pin.getBytes());
|
||||
if ( ! card(isoDep, login).equals("9000")) { // login
|
||||
toast("Pin Error!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID
|
||||
*
|
||||
* @return the user id as "name <email>"
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static String getUserId(IsoDep isoDep) throws IOException {
|
||||
String info = "00CA006500";
|
||||
String data = "00CA005E00";
|
||||
return getName(card(isoDep, info)) + " <" + (new String(Hex.decode(getDataField(card(isoDep, data))))) + ">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the long key ID
|
||||
*
|
||||
* @return the long key id (last 16 bytes of the fingerprint)
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static long getKeyId(IsoDep isoDep) throws IOException {
|
||||
String keyId = nfcGetFingerprint(isoDep).substring(24);
|
||||
Log.d(Constants.TAG, "keyId: " + keyId);
|
||||
return Long.parseLong(keyId, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fingerprint of the signature key
|
||||
*
|
||||
* @return the fingerprint
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public static String nfcGetFingerprint(IsoDep isoDep) throws IOException {
|
||||
String data = "00CA006E00";
|
||||
byte[] buf = isoDep.transceive(Hex.decode(data));
|
||||
|
||||
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
|
||||
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
|
||||
|
||||
Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
|
||||
if (fptlv != null) {
|
||||
ByteBuffer fpbuf = ByteBuffer.wrap(fptlv.mV);
|
||||
byte[] fp = new byte[20];
|
||||
fpbuf.get(fp);
|
||||
Log.d(Constants.TAG, "fingerprint 1: " + KeyFormattingUtils.convertFingerprintToHex(fp));
|
||||
fpbuf.get(fp);
|
||||
Log.d(Constants.TAG, "fingerprint 2: " + KeyFormattingUtils.convertFingerprintToHex(fp));
|
||||
fpbuf.get(fp);
|
||||
Log.d(Constants.TAG, "fingerprint 3: " + KeyFormattingUtils.convertFingerprintToHex(fp));
|
||||
}
|
||||
|
||||
return "nope";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the screen
|
||||
*
|
||||
* @param text the text which should be contained within the toast
|
||||
*/
|
||||
private void toast(String text) {
|
||||
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive new NFC Intents to this activity only by enabling foreground dispatch.
|
||||
* This can only be done in onResume!
|
||||
*/
|
||||
public void enableNfcForegroundDispatch() {
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
Intent nfcI = new Intent(this, NfcIntentActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, 0);
|
||||
IntentFilter[] writeTagFilters = new IntentFilter[]{
|
||||
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
|
||||
};
|
||||
|
||||
// https://code.google.com/p/android/issues/detail?id=62918
|
||||
// maybe mNfcAdapter.enableReaderMode(); ?
|
||||
try {
|
||||
mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e);
|
||||
}
|
||||
Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable foreground dispatch in onPause!
|
||||
*/
|
||||
public void disableNfcForegroundDispatch() {
|
||||
mNfcAdapter.disableForegroundDispatch(this);
|
||||
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the user out of the raw card output regarding card holder related data
|
||||
*
|
||||
* @param name the raw card holder related data from the card
|
||||
* @return the name given in this data
|
||||
*/
|
||||
public static String getName(String name) {
|
||||
String slength;
|
||||
int ilength;
|
||||
name = name.substring(6);
|
||||
slength = name.substring(0, 2);
|
||||
ilength = Integer.parseInt(slength, 16) * 2;
|
||||
name = name.substring(2, ilength + 2);
|
||||
name = (new String(Hex.decode(name))).replace('<', ' ');
|
||||
return (name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the raw data from the card by four characters
|
||||
*
|
||||
* @param output the raw data from the card
|
||||
* @return the data field of that data
|
||||
*/
|
||||
private static String getDataField(String output) {
|
||||
return output.substring(0, output.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicates with the OpenPgpCard via the APDU
|
||||
*
|
||||
* @param hex the hexadecimal APDU
|
||||
* @return The answer from the card
|
||||
* @throws java.io.IOException throws an exception if something goes wrong
|
||||
*/
|
||||
public static String card(IsoDep isoDep, String hex) throws IOException {
|
||||
return getHex(isoDep.transceive(Hex.decode(hex)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array into an hex string
|
||||
*
|
||||
* @param raw the byte array representation
|
||||
* @return the hexadecimal string representation
|
||||
*/
|
||||
public static String getHex(byte[] raw) {
|
||||
return new String(Hex.encode(raw));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
* <p/>
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
public class NfcOperationActivity extends BaseNfcActivity {
|
||||
|
||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||
|
||||
// passthrough for OpenPgpService
|
||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||
|
||||
public static final String RESULT_DATA = "result_data";
|
||||
|
||||
private RequiredInputParcel mRequiredInput;
|
||||
private Intent mServiceIntent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcOperationActivity.onCreate");
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
|
||||
mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT);
|
||||
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
|
||||
|
||||
// obtain passphrase for this subkey
|
||||
obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNfcPerform() throws IOException {
|
||||
|
||||
CryptoInputParcel inputParcel = new CryptoInputParcel(mRequiredInput.mSignatureTime);
|
||||
|
||||
switch (mRequiredInput.mType) {
|
||||
case NFC_DECRYPT: {
|
||||
for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
|
||||
byte[] hash = mRequiredInput.mInputHashes[i];
|
||||
byte[] decryptedSessionKey = nfcDecryptSessionKey(hash);
|
||||
inputParcel.addCryptoData(hash, decryptedSessionKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NFC_SIGN: {
|
||||
for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
|
||||
byte[] hash = mRequiredInput.mInputHashes[i];
|
||||
int algo = mRequiredInput.mSignAlgos[i];
|
||||
byte[] signedHash = nfcCalculateSignature(hash, algo);
|
||||
inputParcel.addCryptoData(hash, signedHash);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mServiceIntent != null) {
|
||||
CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, inputParcel);
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
} else {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel);
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePinError() {
|
||||
|
||||
// avoid a loop
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.useDefaultYubiKeyPin()) {
|
||||
toast(getString(R.string.error_pin_nodefault));
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// clear (invalid) passphrase
|
||||
PassphraseCacheService.clearCachedPassphrase(
|
||||
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
|
||||
|
||||
obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
|
||||
}
|
||||
|
||||
}
|
@ -52,7 +52,10 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@ -63,13 +66,14 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
* This activity encapsulates a DialogFragment to emulate a dialog.
|
||||
*/
|
||||
public class PassphraseDialogActivity extends FragmentActivity {
|
||||
public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
|
||||
public static final String RESULT_CRYPTO_INPUT = "result_data";
|
||||
|
||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||
public static final String EXTRA_SUBKEY_ID = "secret_key_id";
|
||||
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||
|
||||
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
|
||||
|
||||
@ -88,9 +92,27 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
// this activity itself has no content view (see manifest)
|
||||
|
||||
long keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||
long keyId;
|
||||
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
||||
keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||
} else {
|
||||
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
||||
switch (requiredInput.mType) {
|
||||
case PASSPHRASE_SYMMETRIC: {
|
||||
keyId = Constants.key.symmetric;
|
||||
break;
|
||||
}
|
||||
case PASSPHRASE: {
|
||||
keyId = requiredInput.getSubKeyId();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError("Unsupported required input type for PassphraseDialogActivity!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_DATA);
|
||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||
|
||||
show(this, keyId, serviceIntent);
|
||||
}
|
||||
@ -141,7 +163,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(EXTRA_SUBKEY_ID, keyId);
|
||||
args.putParcelable(EXTRA_DATA, serviceIntent);
|
||||
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@ -174,11 +196,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
R.style.Theme_AppCompat_Light_Dialog);
|
||||
|
||||
mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID);
|
||||
mServiceIntent = getArguments().getParcelable(EXTRA_DATA);
|
||||
mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT);
|
||||
|
||||
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);
|
||||
|
||||
alert.setTitle(R.string.title_unlock);
|
||||
// No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
|
||||
//alert.setTitle()
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(theme);
|
||||
View view = inflater.inflate(R.layout.passphrase_dialog, null);
|
||||
@ -243,8 +266,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
case PASSPHRASE_EMPTY:
|
||||
finishCaching(new Passphrase(""));
|
||||
default:
|
||||
message = "This should not happen!";
|
||||
break;
|
||||
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
||||
}
|
||||
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
@ -296,7 +318,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
|
||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubikeyPin()) {
|
||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) {
|
||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
} else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
|
||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
@ -309,7 +331,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
AlertDialog dialog = alert.create();
|
||||
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
activity.getString(android.R.string.ok), (DialogInterface.OnClickListener) null);
|
||||
activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
@ -406,17 +428,14 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase);
|
||||
if (mServiceIntent != null) {
|
||||
// TODO: Not routing passphrase through OpenPGP API currently
|
||||
// due to security concerns...
|
||||
// BUT this means you need to _cache_ passphrases!
|
||||
CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel);
|
||||
getActivity().setResult(RESULT_OK, mServiceIntent);
|
||||
} else {
|
||||
// also return passphrase back to activity
|
||||
Intent returnIntent = new Intent();
|
||||
returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase);
|
||||
returnIntent.putExtra(EXTRA_KEY_ID, mSecretRing.getMasterKeyId());
|
||||
returnIntent.putExtra(EXTRA_SUBKEY_ID, mSubKeyId);
|
||||
returnIntent.putExtra(RESULT_CRYPTO_INPUT, inputParcel);
|
||||
getActivity().setResult(RESULT_OK, returnIntent);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
@ -107,10 +107,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
|
||||
initializeUseDefaultYubikeyPin(
|
||||
initializeUseDefaultYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
|
||||
|
||||
initializeUseNumKeypadForYubikeyPin(
|
||||
initializeUseNumKeypadForYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
||||
|
||||
}
|
||||
@ -262,10 +262,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
|
||||
initializeUseDefaultYubikeyPin(
|
||||
initializeUseDefaultYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
|
||||
|
||||
initializeUseNumKeypadForYubikeyPin(
|
||||
initializeUseNumKeypadForYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
||||
}
|
||||
}
|
||||
@ -335,23 +335,23 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver();
|
||||
}
|
||||
|
||||
private static void initializeUseDefaultYubikeyPin(final CheckBoxPreference mUseDefaultYubikeyPin) {
|
||||
mUseDefaultYubikeyPin.setChecked(sPreferences.useDefaultYubikeyPin());
|
||||
mUseDefaultYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) {
|
||||
mUseDefaultYubiKeyPin.setChecked(sPreferences.useDefaultYubiKeyPin());
|
||||
mUseDefaultYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mUseDefaultYubikeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseDefaultYubikeyPin((Boolean) newValue);
|
||||
mUseDefaultYubiKeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseDefaultYubiKeyPin((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeUseNumKeypadForYubikeyPin(final CheckBoxPreference mUseNumKeypadForYubikeyPin) {
|
||||
mUseNumKeypadForYubikeyPin.setChecked(sPreferences.useNumKeypadForYubikeyPin());
|
||||
mUseNumKeypadForYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
private static void initializeUseNumKeypadForYubiKeyPin(final CheckBoxPreference mUseNumKeypadForYubiKeyPin) {
|
||||
mUseNumKeypadForYubiKeyPin.setChecked(sPreferences.useNumKeypadForYubiKeyPin());
|
||||
mUseNumKeypadForYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mUseNumKeypadForYubikeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseNumKeypadForYubikeyPin((Boolean) newValue);
|
||||
mUseNumKeypadForYubiKeyPin.setChecked((Boolean) newValue);
|
||||
sPreferences.setUseNumKeypadForYubiKeyPin((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
|
||||
|
@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
@ -61,18 +61,23 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
@ -80,15 +85,21 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.NfcHelper;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ViewKeyActivity extends BaseActivity implements
|
||||
public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
||||
public static final String EXTRA_NFC_AID = "nfc_aid";
|
||||
public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
|
||||
|
||||
static final int REQUEST_QR_FINGERPRINT = 1;
|
||||
static final int REQUEST_DELETE = 2;
|
||||
static final int REQUEST_EXPORT = 3;
|
||||
public static final String EXTRA_DISPLAY_RESULT = "display_result";
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
ProviderHelper mProviderHelper;
|
||||
@ -108,6 +119,8 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
private ImageView mQrCode;
|
||||
private CardView mQrCodeLayout;
|
||||
|
||||
private String mQrCodeLoaded;
|
||||
|
||||
// NFC
|
||||
private NfcHelper mNfcHelper;
|
||||
|
||||
@ -257,6 +270,21 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mNfcHelper = new NfcHelper(this, mProviderHelper);
|
||||
mNfcHelper.initNfc(mDataUri);
|
||||
|
||||
if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) {
|
||||
OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT);
|
||||
result.createNotify(this).show();
|
||||
}
|
||||
|
||||
startFragment(savedInstanceState, mDataUri);
|
||||
|
||||
if (savedInstanceState == null && getIntent().hasExtra(EXTRA_NFC_AID)) {
|
||||
Intent intent = getIntent();
|
||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
||||
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
|
||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
|
||||
showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -264,6 +292,35 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
setContentView(R.layout.view_key_activity);
|
||||
}
|
||||
|
||||
private void startFragment(Bundle savedInstanceState, final Uri dataUri) {
|
||||
// If we're being restored from a previous state, then we don't
|
||||
// need to do anything and should return or else we could end
|
||||
// up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
if (manager.getBackStackEntryCount() == 0) {
|
||||
// Create an instance of the fragment
|
||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri);
|
||||
manager.beginTransaction()
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
manager.popBackStack();
|
||||
} else {
|
||||
// not sure yet if we actually want this!
|
||||
// manager.popBackStack();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@ -507,6 +564,72 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNfcPerform() throws IOException {
|
||||
|
||||
final byte[] nfcFingerprints = nfcGetFingerprints();
|
||||
final String nfcUserId = nfcGetUserId();
|
||||
final byte[] nfcAid = nfcGetAid();
|
||||
|
||||
String fp = KeyFormattingUtils.convertFingerprintToHex(nfcFingerprints);
|
||||
final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
|
||||
|
||||
if (!mFingerprint.equals(fp)) {
|
||||
try {
|
||||
CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||
Style.WARN, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_yubikey_view).show();
|
||||
return;
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||
Style.WARN, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
ViewKeyActivity.this, CreateKeyActivity.class);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_yubikey_import).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
|
||||
|
||||
}
|
||||
|
||||
public void showYubiKeyFragment(byte[] nfcFingerprints, String nfcUserId, byte[] nfcAid) {
|
||||
ViewKeyYubiKeyFragment frag = ViewKeyYubiKeyFragment.newInstance(
|
||||
nfcFingerprints, nfcUserId, nfcAid);
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
|
||||
manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
manager.beginTransaction()
|
||||
.addToBackStack("yubikey")
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void encrypt(Uri dataUri, boolean text) {
|
||||
// If there is no encryption key, don't bother.
|
||||
if (!mHasEncrypt) {
|
||||
@ -638,6 +761,7 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap qrCode) {
|
||||
mQrCodeLoaded = fingerprint;
|
||||
// scale the image up to our actual size. we do this in code rather
|
||||
// than let the ImageView do this because we don't require filtering.
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||
@ -705,25 +829,13 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
|
||||
case LOADER_ID_UNIFIED: {
|
||||
// if there is no data, just break
|
||||
|
||||
if (!data.moveToFirst()) {
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
String oldFingerprint = mFingerprint;
|
||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
byte[] fpData = data.getBlob(INDEX_FINGERPRINT);
|
||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(fpData);
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
|
||||
mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
||||
mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
||||
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
startFragment(mIsSecret, fpData);
|
||||
|
||||
// get name, email, and comment from USER_ID
|
||||
KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
|
||||
if (mainUserId.name != null) {
|
||||
@ -732,6 +844,15 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mName.setText(R.string.user_id_no_name);
|
||||
}
|
||||
|
||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT));
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
|
||||
mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
|
||||
mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
|
||||
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
|
||||
|
||||
// if the refresh animation isn't playing
|
||||
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
|
||||
// re-create options menu based on mIsSecret, mIsVerified
|
||||
@ -768,7 +889,6 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
} else if (mIsExpired) {
|
||||
if (mIsSecret) {
|
||||
mStatusText.setText(R.string.view_key_expired_secret);
|
||||
mName.setText(mainUserId.name);
|
||||
} else {
|
||||
mStatusText.setText(R.string.view_key_expired);
|
||||
}
|
||||
@ -787,7 +907,7 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mStatusImage.setVisibility(View.GONE);
|
||||
color = getResources().getColor(R.color.primary);
|
||||
// reload qr code only if the fingerprint changed
|
||||
if (!mFingerprint.equals(oldFingerprint)) {
|
||||
if (!mFingerprint.equals(mQrCodeLoaded)) {
|
||||
loadQrCode(mFingerprint);
|
||||
}
|
||||
photoTask.execute(mMasterKeyId);
|
||||
@ -873,6 +993,7 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
mStatusImage.setAlpha(80);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -882,27 +1003,4 @@ public class ViewKeyActivity extends BaseActivity implements
|
||||
|
||||
}
|
||||
|
||||
private void startFragment(final boolean isSecret, final byte[] fingerprint) {
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
if (manager.getBackStackEntryCount() == 0) {
|
||||
// Create an instance of the fragment
|
||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(
|
||||
mDataUri, isSecret, fingerprint);
|
||||
manager.beginTransaction()
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
manager.popBackStack();
|
||||
} else {
|
||||
// not sure yet if we actually want this!
|
||||
// manager.popBackStack();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
|
@ -30,6 +30,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.transition.Fade;
|
||||
@ -48,11 +49,11 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@ -62,21 +63,26 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
private static final String ARG_IS_SECRET = "is_secret";
|
||||
|
||||
private ListView mUserIds;
|
||||
//private ListView mLinkedSystemContact;
|
||||
|
||||
boolean mIsSecret = false;
|
||||
boolean mSystemContactLoaded = false;
|
||||
|
||||
CardView mSystemContactCard;
|
||||
LinearLayout mSystemContactLayout;
|
||||
ImageView mSystemContactPicture;
|
||||
TextView mSystemContactName;
|
||||
|
||||
private static final int LOADER_ID_USER_IDS = 0;
|
||||
private static final int LOADER_ID_LINKED_IDS = 1;
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
private static final int LOADER_ID_USER_IDS = 1;
|
||||
private static final int LOADER_ID_LINKED_CONTACT = 2;
|
||||
private static final int LOADER_ID_LINKED_IDS = 3;
|
||||
|
||||
private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
|
||||
= "loader_linked_contact_master_key_id";
|
||||
private static final String LOADER_EXTRA_LINKED_CONTACT_IS_SECRET
|
||||
= "loader_linked_contact_is_secret";
|
||||
|
||||
private UserIdsAdapter mUserIdsAdapter;
|
||||
private LinkedIdsAdapter mLinkedIdsAdapter;
|
||||
@ -90,35 +96,16 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ViewKeyFragment newInstance(Uri dataUri, boolean isSecret, byte[] fingerprint) {
|
||||
public static ViewKeyFragment newInstance(Uri dataUri) {
|
||||
ViewKeyFragment frag = new ViewKeyFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putBoolean(ARG_IS_SECRET, isSecret);
|
||||
args.putByteArray(ARG_FINGERPRINT, fingerprint);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
Uri dataUri = args.getParcelable(ARG_DATA_URI);
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
boolean isSecret = args.getBoolean(ARG_IS_SECRET);
|
||||
byte[] fingerprint = args.getByteArray(ARG_FINGERPRINT);
|
||||
|
||||
loadData(dataUri, isSecret, fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
@ -144,6 +131,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
});
|
||||
|
||||
mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card);
|
||||
mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout);
|
||||
mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name);
|
||||
mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture);
|
||||
@ -209,60 +197,64 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a system contact exists for given masterKeyId, and if it does, sets name, picture
|
||||
* and onClickListener for the linked system contact's layout
|
||||
* In the case of a secret key, "me" contact details are loaded
|
||||
*
|
||||
* @param masterKeyId
|
||||
* Hides card if no linked system contact exists. Sets name, picture
|
||||
* and onClickListener for the linked system contact's layout.
|
||||
* In the case of a secret key, "me" (own profile) contact details are loaded.
|
||||
*/
|
||||
private void loadLinkedSystemContact(final long masterKeyId) {
|
||||
private void loadLinkedSystemContact(final long contactId) {
|
||||
|
||||
final Context context = mSystemContactName.getContext();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
long contactId;
|
||||
String contactName = null;
|
||||
|
||||
if (mIsSecret) {//all secret keys are linked to "me" profile in contacts
|
||||
contactId = ContactHelper.getMainProfileContactId(resolver);
|
||||
List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context);
|
||||
if (mainProfileNames != null && mainProfileNames.size() > 0) {
|
||||
contactName = mainProfileNames.get(0);
|
||||
}
|
||||
|
||||
} else {
|
||||
contactId = ContactHelper.findContactId(resolver, masterKeyId);
|
||||
contactName = ContactHelper.getContactName(resolver, contactId);
|
||||
}
|
||||
|
||||
if (contactName != null) {//contact name exists for given master key
|
||||
showLinkedSystemContact();
|
||||
|
||||
mSystemContactName.setText(contactName);
|
||||
|
||||
Bitmap picture;
|
||||
if (mIsSecret) {
|
||||
picture = ContactHelper.loadMainProfilePhoto(resolver, false);
|
||||
} else {
|
||||
picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, false);
|
||||
picture = ContactHelper.loadPhotoByContactId(resolver, contactId, false);
|
||||
}
|
||||
if (picture != null) mSystemContactPicture.setImageBitmap(picture);
|
||||
|
||||
final long finalContactId = contactId;
|
||||
mSystemContactLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
launchContactActivity(finalContactId, context);
|
||||
launchContactActivity(contactId, context);
|
||||
}
|
||||
});
|
||||
mSystemContactLoaded = true;
|
||||
} else {
|
||||
hideLinkedSystemContact();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideLinkedSystemContact() {
|
||||
mSystemContactCard.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showLinkedSystemContact() {
|
||||
mSystemContactCard.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* launches the default android Contacts app to view a contact with the passed
|
||||
* contactId (CONTACT_ID column from ContactsContract.RawContact table which is _ID column in
|
||||
* ContactsContract.Contact table)
|
||||
*
|
||||
* @param contactId _ID for row in ContactsContract.Contacts table
|
||||
* @param context
|
||||
*/
|
||||
private void launchContactActivity(final long contactId, Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
@ -271,23 +263,61 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri, boolean isSecret, byte[] fingerprint) {
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
loadData(dataUri);
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] UNIFIED_PROJECTION = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_REVOKED,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
KeychainContract.KeyRings.VERIFIED,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
||||
KeychainContract.KeyRings.FINGERPRINT,
|
||||
KeychainContract.KeyRings.HAS_ENCRYPT
|
||||
};
|
||||
|
||||
static final int INDEX_MASTER_KEY_ID = 1;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_USER_ID = 2;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_IS_REVOKED = 3;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_IS_EXPIRED = 4;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_VERIFIED = 5;
|
||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
||||
static final int INDEX_FINGERPRINT = 7;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_HAS_ENCRYPT = 8;
|
||||
|
||||
private static final String[] RAWCONTACT_PROJECTION = {
|
||||
ContactsContract.RawContacts.CONTACT_ID
|
||||
};
|
||||
|
||||
private static final int INDEX_CONTACT_ID = 0;
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
mDataUri = dataUri;
|
||||
mIsSecret = isSecret;
|
||||
mFingerprint = fingerprint;
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri);
|
||||
|
||||
// load user ids after we know if it's a secret key
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
|
||||
mLinkedIdsAdapter = new LinkedIdsAdapter(getActivity(), null, 0,
|
||||
mIsSecret, mLinkedIdsExpander);
|
||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -295,11 +325,45 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
setContentShown(false);
|
||||
|
||||
switch (id) {
|
||||
case LOADER_ID_USER_IDS:
|
||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
case LOADER_ID_UNIFIED: {
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS:
|
||||
case LOADER_ID_USER_IDS: {
|
||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
}
|
||||
|
||||
//we need a separate loader for linked contact to ensure refreshing on verification
|
||||
case LOADER_ID_LINKED_CONTACT: {
|
||||
//passed in args to explicitly specify their need
|
||||
long masterKeyId = args.getLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID);
|
||||
boolean isSecret = args.getBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET);
|
||||
|
||||
Uri baseUri;
|
||||
if (isSecret)
|
||||
baseUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI;
|
||||
else
|
||||
baseUri = ContactsContract.RawContacts.CONTENT_URI;
|
||||
|
||||
return new CursorLoader(
|
||||
getActivity(),
|
||||
baseUri,
|
||||
RAWCONTACT_PROJECTION,
|
||||
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
|
||||
ContactsContract.RawContacts.SOURCE_ID + "=? AND " +
|
||||
ContactsContract.RawContacts.DELETED + "=?",
|
||||
new String[]{//"0" for "not deleted"
|
||||
Constants.ACCOUNT_TYPE,
|
||||
Long.toString(masterKeyId),
|
||||
"0"
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
@ -307,21 +371,71 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
/* TODO better error handling? May cause problems when a key is deleted,
|
||||
* because the notification triggers faster than the activity closes.
|
||||
*/
|
||||
// Avoid NullPointerExceptions...
|
||||
if (data.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||
|
||||
// load user ids after we know if it's a secret key
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
|
||||
mLinkedIdsAdapter =
|
||||
new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander);
|
||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
||||
|
||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
// we need to load linked contact here to prevent lag introduced by loader
|
||||
// for the linked contact
|
||||
long contactId = ContactHelper.findContactId(
|
||||
getActivity().getContentResolver(),
|
||||
masterKeyId);
|
||||
loadLinkedSystemContact(contactId);
|
||||
|
||||
Bundle linkedContactData = new Bundle();
|
||||
linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
|
||||
linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, mIsSecret);
|
||||
|
||||
// initialises loader for contact query so we can listen to any updates
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case LOADER_ID_USER_IDS: {
|
||||
mUserIdsAdapter.swapCursor(cursor);
|
||||
loadLinkedSystemContact(KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint));
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
mLinkedIdsCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(cursor);
|
||||
mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_CONTACT: {
|
||||
if (data.moveToFirst()) {// if we have a linked contact
|
||||
long contactId = data.getLong(INDEX_CONTACT_ID);
|
||||
loadLinkedSystemContact(contactId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
public class ViewKeyYubiKeyFragment extends Fragment
|
||||
implements LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_FINGERPRINT = "fingerprint";
|
||||
public static final String ARG_USER_ID = "user_id";
|
||||
public static final String ARG_CARD_AID = "aid";
|
||||
private byte[][] mFingerprints;
|
||||
private String mUserId;
|
||||
private byte[] mCardAid;
|
||||
private long mMasterKeyId;
|
||||
private Button vButton;
|
||||
private TextView vStatus;
|
||||
|
||||
public static ViewKeyYubiKeyFragment newInstance(byte[] fingerprints, String userId, byte[] aid) {
|
||||
ViewKeyYubiKeyFragment frag = new ViewKeyYubiKeyFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_FINGERPRINT, fingerprints);
|
||||
args.putString(ARG_USER_ID, userId);
|
||||
args.putByteArray(ARG_CARD_AID, aid);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT));
|
||||
mFingerprints = new byte[buf.remaining()/40][];
|
||||
for (int i = 0; i < mFingerprints.length; i++) {
|
||||
mFingerprints[i] = new byte[20];
|
||||
buf.get(mFingerprints[i]);
|
||||
}
|
||||
mUserId = args.getString(ARG_USER_ID);
|
||||
mCardAid = args.getByteArray(ARG_CARD_AID);
|
||||
|
||||
mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[0]);
|
||||
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.view_key_yubikey, null);
|
||||
|
||||
TextView vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
|
||||
TextView vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
|
||||
|
||||
String serno = Hex.toHexString(mCardAid, 10, 4);
|
||||
vSerNo.setText(getString(R.string.yubikey_serno, serno));
|
||||
|
||||
if (!mUserId.isEmpty()) {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder, mUserId));
|
||||
} else {
|
||||
vUserId.setText(getString(R.string.yubikey_key_holder_unset));
|
||||
}
|
||||
|
||||
vButton = (Button) view.findViewById(R.id.button_bind);
|
||||
vButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
promoteToSecretKey();
|
||||
}
|
||||
});
|
||||
|
||||
vStatus = (TextView) view.findViewById(R.id.yubikey_status);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void promoteToSecretKey() {
|
||||
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
PromoteKeyResult result =
|
||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
||||
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
|
||||
}
|
||||
|
||||
public static final String[] PROJECTION = new String[]{
|
||||
Keys._ID,
|
||||
Keys.KEY_ID,
|
||||
Keys.RANK,
|
||||
Keys.HAS_SECRET,
|
||||
Keys.FINGERPRINT
|
||||
};
|
||||
private static final int INDEX_KEY_ID = 1;
|
||||
private static final int INDEX_RANK = 2;
|
||||
private static final int INDEX_HAS_SECRET = 3;
|
||||
private static final int INDEX_FINGERPRINT = 4;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId),
|
||||
PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if (!data.moveToFirst()) {
|
||||
// wut?
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allBound = true;
|
||||
boolean noneBound = true;
|
||||
|
||||
do {
|
||||
SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET));
|
||||
byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||
Integer index = naiveIndexOf(mFingerprints, fingerprint);
|
||||
if (index == null) {
|
||||
continue;
|
||||
}
|
||||
if (keyType == SecretKeyType.DIVERT_TO_CARD) {
|
||||
noneBound = false;
|
||||
} else {
|
||||
allBound = false;
|
||||
}
|
||||
} while (data.moveToNext());
|
||||
|
||||
if (allBound) {
|
||||
vButton.setVisibility(View.GONE);
|
||||
vStatus.setText(R.string.yubikey_status_bound);
|
||||
} else {
|
||||
vButton.setVisibility(View.VISIBLE);
|
||||
vStatus.setText(noneBound
|
||||
? R.string.yubikey_status_unbound
|
||||
: R.string.yubikey_status_partly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Integer naiveIndexOf(byte[][] haystack, byte[] needle) {
|
||||
for (int i = 0; i < haystack.length; i++) {
|
||||
if (Arrays.equals(needle, haystack[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
|
||||
}
|
||||
}
|
@ -147,6 +147,8 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mStatus.setVisibility(View.GONE);
|
||||
if (mSlingerButton.hasOnClickListeners()) {
|
||||
mSlinger.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mSlinger.setVisibility(View.GONE);
|
||||
}
|
||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
|
||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* https://gist.github.com/yrom/3b4bcbc2370ca2290434
|
||||
*/
|
||||
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
|
||||
private int space;
|
||||
private int spanCount;
|
||||
private int lastItemInFirstLane = -1;
|
||||
|
||||
public SpacesItemDecoration(int space) {
|
||||
this(space, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param space
|
||||
* @param spanCount spans count of one lane
|
||||
*/
|
||||
public SpacesItemDecoration(int space, int spanCount) {
|
||||
this.space = space;
|
||||
this.spanCount = spanCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
|
||||
final int position = params.getViewPosition();
|
||||
final int spanSize;
|
||||
final int index;
|
||||
if (params instanceof GridLayoutManager.LayoutParams) {
|
||||
GridLayoutManager.LayoutParams gridParams = (GridLayoutManager.LayoutParams) params;
|
||||
spanSize = gridParams.getSpanSize();
|
||||
index = gridParams.getSpanIndex();
|
||||
} else {
|
||||
spanSize = 1;
|
||||
index = position % spanCount;
|
||||
}
|
||||
// invalid value
|
||||
if (spanSize < 1 || index < 0) return;
|
||||
|
||||
if (spanSize == spanCount) { // full span
|
||||
outRect.left = space;
|
||||
outRect.right = space;
|
||||
} else {
|
||||
if (index == 0) { // left one
|
||||
outRect.left = space;
|
||||
}
|
||||
// spanCount >= 1
|
||||
if (index == spanCount - 1) { // right one
|
||||
outRect.right = space;
|
||||
}
|
||||
if (outRect.left == 0) {
|
||||
outRect.left = space / 2;
|
||||
}
|
||||
if (outRect.right == 0) {
|
||||
outRect.right = space / 2;
|
||||
}
|
||||
}
|
||||
// set top to all in first lane
|
||||
if (position < spanCount && spanSize <= spanCount) {
|
||||
if (lastItemInFirstLane < 0) { // lay out at first time
|
||||
lastItemInFirstLane = position + spanSize == spanCount ? position : lastItemInFirstLane;
|
||||
outRect.top = space;
|
||||
} else if (position <= lastItemInFirstLane) { // scroll to first lane again
|
||||
outRect.top = space;
|
||||
}
|
||||
}
|
||||
outRect.bottom = space;
|
||||
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
@ -63,8 +63,8 @@ public abstract class BaseActivity extends ActionBarActivity {
|
||||
* Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines
|
||||
* see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
|
||||
*/
|
||||
protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
|
||||
View.OnClickListener cancelOnClickListener) {
|
||||
public void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
|
||||
View.OnClickListener cancelOnClickListener) {
|
||||
setActionBarIcon(R.drawable.ic_close_white_24dp);
|
||||
|
||||
// Inflate the custom action bar view
|
@ -1,131 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
public abstract class BaseNfcActivity extends BaseActivity {
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
*
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public class NfcActivity extends BaseActivity {
|
||||
|
||||
// actions
|
||||
public static final String ACTION_SIGN_HASH = "sign_hash";
|
||||
public static final String ACTION_DECRYPT_SESSION_KEY = "decrypt_session_key";
|
||||
|
||||
// always
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_PIN = "pin";
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
// sign
|
||||
public static final String EXTRA_NFC_HASH_TO_SIGN = "nfc_hash";
|
||||
public static final String EXTRA_NFC_HASH_ALGO = "nfc_hash_algo";
|
||||
|
||||
// decrypt
|
||||
public static final String EXTRA_NFC_ENC_SESSION_KEY = "encrypted_session_key";
|
||||
|
||||
private Intent mServiceIntent;
|
||||
|
||||
private static final int TIMEOUT = 100000;
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
||||
|
||||
protected Passphrase mPin;
|
||||
private NfcAdapter mNfcAdapter;
|
||||
private IsoDep mIsoDep;
|
||||
private String mAction;
|
||||
|
||||
private String mPin;
|
||||
private Long mKeyId;
|
||||
|
||||
// sign
|
||||
private byte[] mHashToSign;
|
||||
private int mHashAlgo;
|
||||
|
||||
// decrypt
|
||||
private byte[] mEncryptedSessionKey;
|
||||
private static final int TIMEOUT = 100000;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(Constants.TAG, "NfcActivity.onCreate");
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
String action = intent.getAction();
|
||||
|
||||
// if we get are passed a key id, save it for the check
|
||||
if (data.containsKey(EXTRA_KEY_ID)) {
|
||||
mKeyId = data.getLong(EXTRA_KEY_ID);
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
|
||||
throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!");
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case ACTION_SIGN_HASH:
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN);
|
||||
mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
break;
|
||||
case ACTION_DECRYPT_SESSION_KEY:
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
break;
|
||||
case NfcAdapter.ACTION_TAG_DISCOVERED:
|
||||
Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!");
|
||||
toast("This should not happen! Please create a new bug report that the NFC screen is restarted!");
|
||||
finish();
|
||||
break;
|
||||
default:
|
||||
Log.d(Constants.TAG, "Action not supported: " + action);
|
||||
break;
|
||||
/**
|
||||
* This activity is started as a singleTop activity.
|
||||
* All new NFC Intents which are delivered to this activity are handled here
|
||||
*/
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
try {
|
||||
handleNdefDiscoveredIntent(intent);
|
||||
} catch (IOException e) {
|
||||
handleNfcError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
public void handleNfcError(IOException e) {
|
||||
|
||||
Log.e(Constants.TAG, "nfc error", e);
|
||||
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
||||
|
||||
}
|
||||
|
||||
public void handlePinError() {
|
||||
toast("Wrong PIN!");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +108,7 @@ public class NfcActivity extends BaseActivity {
|
||||
*/
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
Log.d(Constants.TAG, "NfcActivity.onPause");
|
||||
Log.d(Constants.TAG, "BaseNfcActivity.onPause");
|
||||
|
||||
disableNfcForegroundDispatch();
|
||||
}
|
||||
@ -145,25 +119,45 @@ public class NfcActivity extends BaseActivity {
|
||||
*/
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(Constants.TAG, "NfcActivity.onResume");
|
||||
Log.d(Constants.TAG, "BaseNfcActivity.onResume");
|
||||
|
||||
enableNfcForegroundDispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* This activity is started as a singleTop activity.
|
||||
* All new NFC Intents which are delivered to this activity are handled here
|
||||
*/
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
try {
|
||||
handleNdefDiscoveredIntent(intent);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "Connection error!", e);
|
||||
toast("Connection Error: " + e.getMessage());
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) {
|
||||
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.useDefaultYubiKeyPin()) {
|
||||
mPin = new Passphrase("123456");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
|
||||
RequiredInputParcel.createRequiredPassphrase(requiredInput));
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
|
||||
}
|
||||
|
||||
protected void setYubiKeyPin(Passphrase pin) {
|
||||
mPin = pin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE:
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
setResult(resultCode);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||
mPin = input.getPassphrase();
|
||||
break;
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,7 +175,7 @@ public class NfcActivity extends BaseActivity {
|
||||
* on ISO SmartCard Systems specification.
|
||||
*
|
||||
*/
|
||||
private void handleNdefDiscoveredIntent(Intent intent) throws IOException {
|
||||
protected void handleNdefDiscoveredIntent(Intent intent) throws IOException {
|
||||
|
||||
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
|
||||
@ -196,103 +190,54 @@ public class NfcActivity extends BaseActivity {
|
||||
|
||||
// Command APDU (page 51) for SELECT FILE command (page 29)
|
||||
String opening =
|
||||
"00" // CLA
|
||||
+ "A4" // INS
|
||||
+ "04" // P1
|
||||
+ "00" // P2
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if ( ! card(opening).equals(accepted)) { // activate connection
|
||||
toast("Opening Error!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
"00" // CLA
|
||||
+ "A4" // INS
|
||||
+ "04" // P1
|
||||
+ "00" // P2
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection
|
||||
throw new IOException("Initialization failed!");
|
||||
}
|
||||
|
||||
// Command APDU for VERIFY command (page 32)
|
||||
String login =
|
||||
"00" // CLA
|
||||
+ "20" // INS
|
||||
+ "00" // P1
|
||||
+ "82" // P2 (PW1)
|
||||
+ String.format("%02x", mPin.length()) // Lc
|
||||
+ Hex.toHexString(mPin.getBytes());
|
||||
if ( ! card(login).equals(accepted)) { // login
|
||||
toast("Wrong PIN!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
onNfcPerform();
|
||||
|
||||
if (ACTION_SIGN_HASH.equals(mAction)) {
|
||||
|
||||
// If we were supplied with a key id for checking, do so
|
||||
if (mKeyId != null) {
|
||||
// For signing, we check the master key
|
||||
long keyId = nfcGetKeyId(mIsoDep, 0);
|
||||
// If it's wrong, just cancel
|
||||
if (keyId != mKeyId) {
|
||||
toast("NFC Tag has wrong signing key id!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// returns signed hash
|
||||
byte[] signedHash = nfcCalculateSignature(mHashToSign, mHashAlgo);
|
||||
|
||||
if (signedHash == null) {
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity signedHash as hex: " + getHex(signedHash));
|
||||
|
||||
// give data through for new service call
|
||||
// OpenPgpApi.EXTRA_NFC_SIGNED_HASH
|
||||
mServiceIntent.putExtra("nfc_signed_hash", signedHash);
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
finish();
|
||||
|
||||
} else if (ACTION_DECRYPT_SESSION_KEY.equals(mAction)) {
|
||||
|
||||
// If we were supplied with a key id for checking, do so
|
||||
if (mKeyId != null) {
|
||||
// For decryption, we check the confidentiality key
|
||||
long keyId = nfcGetKeyId(mIsoDep, 1);
|
||||
// If it's wrong, just cancel
|
||||
if (keyId != mKeyId) {
|
||||
toast("NFC Tag has wrong encryption key id!");
|
||||
setResult(RESULT_CANCELED, mServiceIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] decryptedSessionKey = nfcDecryptSessionKey(mEncryptedSessionKey);
|
||||
|
||||
// give data through for new service call
|
||||
// OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY
|
||||
mServiceIntent.putExtra("nfc_decrypted_session_key", decryptedSessionKey);
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
finish();
|
||||
}
|
||||
mIsoDep.close();
|
||||
mIsoDep = null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID
|
||||
*
|
||||
* @return the user id as "name <email>"
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public String getUserId() throws IOException {
|
||||
String info = "00CA006500";
|
||||
String data = "00CA005E00";
|
||||
return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">";
|
||||
protected void onNfcPerform() throws IOException {
|
||||
|
||||
final byte[] nfcFingerprints = nfcGetFingerprints();
|
||||
final String nfcUserId = nfcGetUserId();
|
||||
final byte[] nfcAid = nfcGetAid();
|
||||
|
||||
final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
|
||||
|
||||
try {
|
||||
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
|
||||
ring.getMasterKeyId();
|
||||
|
||||
Intent intent = new Intent(
|
||||
BaseNfcActivity.this, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Intent intent = new Intent(
|
||||
BaseNfcActivity.this, CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Return the key id from application specific data stored on tag, or null
|
||||
@ -301,8 +246,8 @@ public class NfcActivity extends BaseActivity {
|
||||
* @param idx Index of the key to return the fingerprint from.
|
||||
* @return The long key id of the requested key, or null if not found.
|
||||
*/
|
||||
public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException {
|
||||
byte[] fp = nfcGetFingerprint(isoDep, idx);
|
||||
public Long nfcGetKeyId(int idx) throws IOException {
|
||||
byte[] fp = nfcGetFingerprint(idx);
|
||||
if (fp == null) {
|
||||
return null;
|
||||
}
|
||||
@ -318,9 +263,9 @@ public class NfcActivity extends BaseActivity {
|
||||
*
|
||||
* @return The fingerprints of all subkeys in a contiguous byte array.
|
||||
*/
|
||||
public static byte[] nfcGetFingerprints(IsoDep isoDep) throws IOException {
|
||||
public byte[] nfcGetFingerprints() throws IOException {
|
||||
String data = "00CA006E00";
|
||||
byte[] buf = isoDep.transceive(Hex.decode(data));
|
||||
byte[] buf = mIsoDep.transceive(Hex.decode(data));
|
||||
|
||||
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
|
||||
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
|
||||
@ -339,26 +284,39 @@ public class NfcActivity extends BaseActivity {
|
||||
* @param idx Index of the key to return the fingerprint from.
|
||||
* @return The fingerprint of the requested key, or null if not found.
|
||||
*/
|
||||
public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException {
|
||||
byte[] data = nfcGetFingerprints(isoDep);
|
||||
public byte[] nfcGetFingerprint(int idx) throws IOException {
|
||||
byte[] data = nfcGetFingerprints();
|
||||
|
||||
// return the master key fingerprint
|
||||
ByteBuffer fpbuf = ByteBuffer.wrap(data);
|
||||
byte[] fp = new byte[20];
|
||||
fpbuf.position(idx*20);
|
||||
fpbuf.position(idx * 20);
|
||||
fpbuf.get(fp, 0, 20);
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
public byte[] nfcGetAid() throws IOException {
|
||||
|
||||
String info = "00CA004F00";
|
||||
return mIsoDep.transceive(Hex.decode(info));
|
||||
|
||||
}
|
||||
|
||||
public String nfcGetUserId() throws IOException {
|
||||
|
||||
String info = "00CA006500";
|
||||
return nfcGetHolderName(nfcCommunicate(info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls to calculate the signature and returns the MPI value
|
||||
*
|
||||
* @param hash the hash for signing
|
||||
* @return a big integer representing the MPI for the given hash
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
|
||||
nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing)
|
||||
|
||||
// dsi, including Lc
|
||||
String dsi;
|
||||
@ -367,7 +325,7 @@ public class NfcActivity extends BaseActivity {
|
||||
switch (hashAlgo) {
|
||||
case HashAlgorithmTags.SHA1:
|
||||
if (hash.length != 20) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
|
||||
}
|
||||
dsi = "23" // Lc
|
||||
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
|
||||
@ -378,45 +336,45 @@ public class NfcActivity extends BaseActivity {
|
||||
break;
|
||||
case HashAlgorithmTags.RIPEMD160:
|
||||
if (hash.length != 20) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
|
||||
}
|
||||
dsi = "233021300906052B2403020105000414" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA224:
|
||||
if (hash.length != 28) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
|
||||
}
|
||||
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA256:
|
||||
if (hash.length != 32) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
|
||||
}
|
||||
dsi = "333031300D060960864801650304020105000420" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA384:
|
||||
if (hash.length != 48) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
|
||||
}
|
||||
dsi = "433041300D060960864801650304020205000430" + getHex(hash);
|
||||
break;
|
||||
case HashAlgorithmTags.SHA512:
|
||||
if (hash.length != 64) {
|
||||
throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!");
|
||||
throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
|
||||
}
|
||||
dsi = "533051300D060960864801650304020305000440" + getHex(hash);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not supported hash algo!");
|
||||
throw new IOException("Not supported hash algo!");
|
||||
}
|
||||
|
||||
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
|
||||
String apdu =
|
||||
"002A9E9A" // CLA, INS, P1, P2
|
||||
+ dsi // digital signature input
|
||||
+ "00"; // Le
|
||||
"002A9E9A" // CLA, INS, P1, P2
|
||||
+ dsi // digital signature input
|
||||
+ "00"; // Le
|
||||
|
||||
String response = card(apdu);
|
||||
String response = nfcCommunicate(apdu);
|
||||
|
||||
// split up response into signature and status
|
||||
String status = response.substring(response.length()-4);
|
||||
@ -426,22 +384,20 @@ public class NfcActivity extends BaseActivity {
|
||||
while (status.substring(0, 2).equals("61")) {
|
||||
Log.d(Constants.TAG, "requesting more data, status " + status);
|
||||
// Send GET RESPONSE command
|
||||
response = card("00C00000" + status.substring(2));
|
||||
response = nfcCommunicate("00C00000" + status.substring(2));
|
||||
status = response.substring(response.length()-4);
|
||||
signature += response.substring(0, response.length()-4);
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "final response:" + status);
|
||||
|
||||
if ( ! status.equals("9000")) {
|
||||
toast("Bad NFC response code: " + status);
|
||||
return null;
|
||||
if ( ! "9000".equals(status)) {
|
||||
throw new IOException("Bad NFC response code: " + status);
|
||||
}
|
||||
|
||||
// Make sure the signature we received is actually the expected number of bytes long!
|
||||
if (signature.length() != 256 && signature.length() != 512) {
|
||||
toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
|
||||
return null;
|
||||
throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
|
||||
}
|
||||
|
||||
return Hex.decode(signature);
|
||||
@ -452,9 +408,10 @@ public class NfcActivity extends BaseActivity {
|
||||
*
|
||||
* @param encryptedSessionKey the encoded session key
|
||||
* @return the decoded session key
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
|
||||
nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption)
|
||||
|
||||
String firstApdu = "102a8086fe";
|
||||
String secondApdu = "002a808603";
|
||||
String le = "00";
|
||||
@ -468,22 +425,48 @@ public class NfcActivity extends BaseActivity {
|
||||
two[i] = encryptedSessionKey[i + one.length + 1];
|
||||
}
|
||||
|
||||
String first = card(firstApdu + getHex(one));
|
||||
String second = card(secondApdu + getHex(two) + le);
|
||||
String first = nfcCommunicate(firstApdu + getHex(one));
|
||||
String second = nfcCommunicate(secondApdu + getHex(two) + le);
|
||||
|
||||
String decryptedSessionKey = getDataField(second);
|
||||
String decryptedSessionKey = nfcGetDataField(second);
|
||||
|
||||
Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey);
|
||||
|
||||
return Hex.decode(decryptedSessionKey);
|
||||
}
|
||||
|
||||
/** Verifies the user's PW1 with the appropriate mode.
|
||||
*
|
||||
* @param mode This is 0x81 for signing, 0x82 for everything else
|
||||
*/
|
||||
public void nfcVerifyPIN(int mode) throws IOException {
|
||||
if (mPin != null) {
|
||||
byte[] pin = new String(mPin.getCharArray()).getBytes();
|
||||
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||
// See specification, page 51
|
||||
String accepted = "9000";
|
||||
|
||||
// Command APDU for VERIFY command (page 32)
|
||||
String login =
|
||||
"00" // CLA
|
||||
+ "20" // INS
|
||||
+ "00" // P1
|
||||
+ String.format("%02x", mode) // P2
|
||||
+ String.format("%02x", pin.length) // Lc
|
||||
+ Hex.toHexString(pin);
|
||||
if (!nfcCommunicate(login).equals(accepted)) { // login
|
||||
handlePinError();
|
||||
throw new IOException("Bad PIN!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the screen
|
||||
*
|
||||
* @param text the text which should be contained within the toast
|
||||
*/
|
||||
private void toast(String text) {
|
||||
protected void toast(String text) {
|
||||
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@ -493,7 +476,7 @@ public class NfcActivity extends BaseActivity {
|
||||
*/
|
||||
public void enableNfcForegroundDispatch() {
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
Intent nfcI = new Intent(this, NfcActivity.class)
|
||||
Intent nfcI = new Intent(this, getClass())
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
IntentFilter[] writeTagFilters = new IntentFilter[]{
|
||||
@ -518,13 +501,7 @@ public class NfcActivity extends BaseActivity {
|
||||
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the user out of the raw card output regarding card holder related data
|
||||
*
|
||||
* @param name the raw card holder related data from the card
|
||||
* @return the name given in this data
|
||||
*/
|
||||
public String getName(String name) {
|
||||
public String nfcGetHolderName(String name) {
|
||||
String slength;
|
||||
int ilength;
|
||||
name = name.substring(6);
|
||||
@ -535,34 +512,16 @@ public class NfcActivity extends BaseActivity {
|
||||
return (name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the raw data from the card by four characters
|
||||
*
|
||||
* @param output the raw data from the card
|
||||
* @return the data field of that data
|
||||
*/
|
||||
private String getDataField(String output) {
|
||||
private String nfcGetDataField(String output) {
|
||||
return output.substring(0, output.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicates with the OpenPgpCard via the APDU
|
||||
*
|
||||
* @param hex the hexadecimal APDU
|
||||
* @return The answer from the card
|
||||
* @throws java.io.IOException throws an exception if something goes wrong
|
||||
*/
|
||||
public String card(String hex) throws IOException {
|
||||
return getHex(mIsoDep.transceive(Hex.decode(hex)));
|
||||
public String nfcCommunicate(String apdu) throws IOException {
|
||||
return getHex(mIsoDep.transceive(Hex.decode(apdu)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array into an hex string
|
||||
*
|
||||
* @param raw the byte array representation
|
||||
* @return the hexadecimal string representation
|
||||
*/
|
||||
public static String getHex(byte[] raw) {
|
||||
return new String(Hex.encode(raw));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
|
||||
/**
|
||||
* All fragments executing crypto operations need to extend this class.
|
||||
*/
|
||||
public abstract class CryptoOperationFragment extends Fragment {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC = 0x00008002;
|
||||
|
||||
private void initiateInputActivity(RequiredInputParcel requiredInput) {
|
||||
|
||||
switch (requiredInput.mType) {
|
||||
case NFC_DECRYPT:
|
||||
case NFC_SIGN: {
|
||||
Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
|
||||
intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
startActivityForResult(intent, REQUEST_CODE_NFC);
|
||||
return;
|
||||
}
|
||||
|
||||
case PASSPHRASE:
|
||||
case PASSPHRASE_SYMMETRIC: {
|
||||
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
|
||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
onCryptoOperationCancelled();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_PASSPHRASE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
CryptoInputParcel cryptoInput =
|
||||
data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||
cryptoOperation(cryptoInput);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_CODE_NFC: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
CryptoInputParcel cryptoInput =
|
||||
data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
|
||||
cryptoOperation(cryptoInput);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean handlePendingMessage(Message message) {
|
||||
|
||||
if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
OperationResult result = data.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
if (result == null || !(result instanceof InputPendingResult)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InputPendingResult pendingResult = (InputPendingResult) result;
|
||||
if (pendingResult.isPending()) {
|
||||
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
|
||||
initiateInputActivity(requiredInput);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void cryptoOperation() {
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
}
|
||||
|
||||
protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
|
||||
|
||||
protected void onCryptoOperationCancelled() {
|
||||
// Nothing to do here, in most cases
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user