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:
Vincent Breitmoser 2015-04-24 14:18:01 +02:00
commit b4aec3114d
257 changed files with 7154 additions and 5101 deletions

12
.gitmodules vendored
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

View File

@ -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

View 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

View File

@ -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"

View File

@ -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'
}
}

View File

@ -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;
}
};
}
}

View File

@ -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());
}
}
}
}

View File

@ -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);

View File

@ -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());

View File

@ -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);

View File

@ -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();
}
{

View File

@ -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',

View File

@ -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" />

View File

@ -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()

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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>() {

View File

@ -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>() {

View File

@ -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;
}
}

View File

@ -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> {

View File

@ -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;
}

View File

@ -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 {
}
};
}
}

View File

@ -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>() {

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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];
}
};
}

View File

@ -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()));
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
/**

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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 {

View File

@ -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 {
}
}
}
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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()]));
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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";

View File

@ -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 =

View File

@ -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.

View File

@ -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) {

View File

@ -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 {

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();
}
}
});
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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) {
}
}

View File

@ -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));

View File

@ -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;
}
}

View File

@ -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

View File

@ -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));
}
}

View File

@ -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