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"] [submodule "extern/spongycastle"]
path = extern/spongycastle path = extern/spongycastle
url = https://github.com/open-keychain/spongycastle.git url = https://github.com/open-keychain/spongycastle.git
ignore = dirty 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"] [submodule "extern/openpgp-api-lib"]
path = extern/openpgp-api-lib path = extern/openpgp-api-lib
url = https://github.com/open-keychain/openpgp-api-lib.git url = https://github.com/open-keychain/openpgp-api-lib.git
@ -26,10 +18,6 @@
path = extern/minidns path = extern/minidns
url = https://github.com/open-keychain/minidns.git url = https://github.com/open-keychain/minidns.git
ignore = dirty ignore = dirty
[submodule "extern/TokenAutoComplete"]
path = extern/TokenAutoComplete
url = https://github.com/open-keychain/TokenAutoComplete
ignore = dirty
[submodule "extern/safeslinger-exchange"] [submodule "extern/safeslinger-exchange"]
path = extern/safeslinger-exchange path = extern/safeslinger-exchange
url = https://github.com/open-keychain/exchange-android 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"?> <?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) --> <!-- 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" <svg
height="626px" viewBox="0 0 626 626" enable-background="new 0 0 626 626" xml:space="preserve"> xmlns:dc="http://purl.org/dc/elements/1.1/"
<g id="Layer_9"> xmlns:cc="http://creativecommons.org/ns#"
<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 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
M313.25,313v18.75h-80.5V292.3c1.324-2.403,2.724-4.778,4.2-7.125V313H313.25z"/> xmlns:svg="http://www.w3.org/2000/svg"
<path fill-rule="evenodd" clip-rule="evenodd" fill="#658D38" d="M232.75,292.3v39.45h80.5V313h3.7v-61h-3.7v-2.75h-44.775 xmlns="http://www.w3.org/2000/svg"
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 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
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 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
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 version="1.1"
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 x="0px"
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 y="0px"
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 width="626px"
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 height="626px"
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 viewBox="0 0 626 626"
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 enable-background="new 0 0 626 626"
C225.71,306.504,228.91,299.271,232.75,292.3z"/> xml:space="preserve"
</g> id="svg2"
<g id="Layer_6"> inkscape:version="0.48.3.1 r9886"
<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 sodipodi:docname="vector-src.svg"><metadata
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 id="metadata36"><rdf:RDF><cc:Work
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 rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
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 rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
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 id="defs34" /><sodipodi:namedview
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 pagecolor="#ffffff"
c2.171,0,4.205-0.35,6.1-1.05C295.588,304.717,296.197,305.575,296.825,306.425z"/> bordercolor="#666666"
<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 borderopacity="1"
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 objecttolerance="10"
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 gridtolerance="10"
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 guidetolerance="10"
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 inkscape:pageopacity="0"
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 inkscape:pageshadow="2"
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 inkscape:window-width="2558"
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 inkscape:window-height="1419"
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 id="namedview32"
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 showgrid="false"
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 inkscape:zoom="0.266577"
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 inkscape:cx="-305.84541"
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 inkscape:cy="354.73238"
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 inkscape:window-x="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 inkscape:window-y="19"
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 inkscape:window-maximized="1"
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 inkscape:current-layer="svg2" />
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 <g
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 id="g3013"
C363.842,340.347,366.242,340.246,368.6,340.05z"/> transform="translate(-20.480691,-1.8781185)"><g
<path fill-rule="evenodd" clip-rule="evenodd" fill="#94C061" d="M301.775,415.525L255.05,462.1 transform="matrix(1.6991779,0,0,1.6991779,-304.41094,-297.68272)"
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 id="Layer_9">
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 <path
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 style="fill:#94c061;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path5"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#658d38;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path7"
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 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"
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 clip-rule="evenodd" />
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 </g><g
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 transform="matrix(1.6991779,0,0,1.6991779,-304.41094,-297.68272)"
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 id="Layer_6">
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 <path
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 style="fill:#bbd89c;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path10"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#7bad45;fill-rule:evenodd"
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"/> inkscape:connector-curvature="0"
<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 id="path12"
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 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"
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 clip-rule="evenodd" />
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 <path
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"/> style="fill:#94c061;fill-rule:evenodd"
<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 inkscape:connector-curvature="0"
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 id="path14"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#6c983d;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path16"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#e2e2e2;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path18"
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 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"
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 clip-rule="evenodd" />
c-1.867-2.054-3.601-4.163-5.2-6.325C296.197,305.575,295.588,304.717,295,303.85z"/> <path
<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 style="fill:#d3d3d3;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path20"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#b6b6b6;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path22"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#a3a3a3;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path24"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#8f8f8f;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path26"
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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#dbdbdb;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
c-0.236,0.257-0.469,0.515-0.7,0.775L302.15,312.6z"/> id="path28"
<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 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"
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 clip-rule="evenodd" />
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 <path
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 style="fill:#c8c8c8;fill-rule:evenodd"
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 inkscape:connector-curvature="0"
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 id="path30"
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 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"
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 clip-rule="evenodd" />
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 </g></g>
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> </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 #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 do
echo $NAME echo $NAME
inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg" inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"

View File

@ -5,7 +5,7 @@ buildscript {
dependencies { dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information // 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;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; 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.InputData;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
@ -152,8 +153,8 @@ public class CertifyOperationTest {
@Test @Test
public void testCertifyId() throws Exception { public void testCertifyId() throws Exception {
CertifyOperation op = operationWithFakePassphraseCache( CertifyOperation op = new CertifyOperation(Robolectric.application,
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); new ProviderHelper(Robolectric.application), null, null);
{ {
CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application)
@ -164,8 +165,8 @@ public class CertifyOperationTest {
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(),
mStaticRing2.getPublicKey().getUnorderedUserIds(), null)); mStaticRing2.getPublicKey().getUnorderedUserIds()));
CertifyResult result = op.certify(actions, null); CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
Assert.assertTrue("certification must succeed", result.success()); Assert.assertTrue("certification must succeed", result.success());
@ -180,8 +181,8 @@ public class CertifyOperationTest {
@Test @Test
public void testCertifyAttribute() throws Exception { public void testCertifyAttribute() throws Exception {
CertifyOperation op = operationWithFakePassphraseCache( CertifyOperation op = new CertifyOperation(Robolectric.application,
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); new ProviderHelper(Robolectric.application), null, null);
{ {
CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application) CanonicalizedPublicKeyRing ring = new ProviderHelper(Robolectric.application)
@ -193,7 +194,7 @@ public class CertifyOperationTest {
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), null, actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), null,
mStaticRing2.getPublicKey().getUnorderedUserAttributes())); 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()); Assert.assertTrue("certification must succeed", result.success());
@ -209,14 +210,14 @@ public class CertifyOperationTest {
@Test @Test
public void testCertifySelf() throws Exception { public void testCertifySelf() throws Exception {
CertifyOperation op = operationWithFakePassphraseCache( CertifyOperation op = new CertifyOperation(Robolectric.application,
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(), mKeyPhrase1); new ProviderHelper(Robolectric.application), null, null);
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId()); CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(), actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(),
mStaticRing2.getPublicKey().getUnorderedUserIds(), null)); 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.assertFalse("certification with itself must fail!", result.success());
Assert.assertTrue("error msg must be about self certification", Assert.assertTrue("error msg must be about self certification",
@ -226,7 +227,8 @@ public class CertifyOperationTest {
@Test @Test
public void testCertifyNonexistent() throws Exception { 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()); CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
@ -234,7 +236,7 @@ public class CertifyOperationTest {
uids.add("nonexistent"); uids.add("nonexistent");
actions.add(new CertifyAction(1234L, uids, null)); 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.assertFalse("certification of nonexistent key must fail", result.success());
Assert.assertTrue("must contain error msg about not found", Assert.assertTrue("must contain error msg about not found",
@ -246,7 +248,7 @@ public class CertifyOperationTest {
actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(), actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(),
mStaticRing2.getPublicKey().getUnorderedUserIds(), null)); 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.assertFalse("certification of nonexistent key must fail", result.success());
Assert.assertTrue("must contain error msg about not found", 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.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider; 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.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; 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.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
@ -101,7 +104,7 @@ public class PromoteKeyOperationTest {
PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application, PromoteKeyOperation op = new PromoteKeyOperation(Robolectric.application,
new ProviderHelper(Robolectric.application), null, null); 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()); Assert.assertTrue("promotion must succeed", result.success());
@ -113,15 +116,35 @@ public class PromoteKeyOperationTest {
Iterator<UncachedPublicKey> it = mStaticRing.getPublicKeys(); Iterator<UncachedPublicKey> it = mStaticRing.getPublicKeys();
while (it.hasNext()) { while (it.hasNext()) {
long keyId = it.next().getKeyId(); 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)); 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.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; 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.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
@ -48,7 +50,6 @@ import java.io.ByteArrayOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.security.Security; import java.security.Security;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@ -138,11 +139,11 @@ public class PgpEncryptDecryptTest {
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput(); PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setSymmetricPassphrase(mPassphrase); b.setSymmetricPassphrase(mPassphrase);
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); 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()); Assert.assertTrue("encryption must succeed", result.success());
@ -159,8 +160,7 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), new ProviderHelper(Robolectric.application),
null, // new DummyPassphraseCache(mPassphrase, 0L), null, // new DummyPassphraseCache(mPassphrase, 0L),
data, out); data, out);
b.setPassphrase(mPassphrase); DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(mPassphrase));
DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption must succeed", result.success()); Assert.assertTrue("decryption must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -182,8 +182,8 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), new ProviderHelper(Robolectric.application),
null, // new DummyPassphraseCache(mPassphrase, 0L), null, // new DummyPassphraseCache(mPassphrase, 0L),
data, out); data, out);
b.setPassphrase(new Passphrase(Arrays.toString(mPassphrase.getCharArray()) + "x")); DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(
DecryptVerifyResult result = b.build().execute(); new Passphrase(new String(mPassphrase.getCharArray()) + "x")));
Assert.assertFalse("decryption must succeed", result.success()); Assert.assertFalse("decryption must succeed", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size()); Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult()); Assert.assertNull("signature should be an error", result.getSignatureResult());
@ -200,7 +200,7 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), new ProviderHelper(Robolectric.application),
null, // new DummyPassphraseCache(mPassphrase, 0L), null, // new DummyPassphraseCache(mPassphrase, 0L),
data, out); data, out);
DecryptVerifyResult result = b.build().execute(); DecryptVerifyResult result = b.build().execute(new CryptoInputParcel());
Assert.assertFalse("decryption must succeed", result.success()); Assert.assertFalse("decryption must succeed", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size()); Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult()); Assert.assertNull("signature should be an error", result.getSignatureResult());
@ -222,11 +222,11 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), null); new ProviderHelper(Robolectric.application), null);
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput(); PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); 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()); Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray(); ciphertext = out.toByteArray();
@ -238,10 +238,8 @@ public class PgpEncryptDecryptTest {
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null); PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null);
b.setPassphrase(mKeyPhrase1); DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(mKeyPhrase1));
DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption with provided passphrase must succeed", result.success()); Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with provided passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with provided passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -264,7 +262,7 @@ public class PgpEncryptDecryptTest {
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null); 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.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -279,11 +277,11 @@ public class PgpEncryptDecryptTest {
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
null, mStaticRing1.getMasterKeyId(), null); 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.assertFalse("decryption with no passphrase must return pending", result.success());
Assert.assertTrue("decryption with no passphrase should return pending", result.isPending()); Assert.assertTrue("decryption with no passphrase should return pending", result.isPending());
Assert.assertEquals("decryption with no passphrase should return pending passphrase", 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()); InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput(); PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[] { b.setEncryptionMasterKeyIds(new long[] {
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(),
mStaticRing2.getMasterKeyId() mStaticRing2.getMasterKeyId()
}); });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); 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()); Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray(); ciphertext = out.toByteArray();
@ -325,7 +323,7 @@ public class PgpEncryptDecryptTest {
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null); 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.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -343,7 +341,7 @@ public class PgpEncryptDecryptTest {
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
// allow only the second to decrypt // allow only the second to decrypt
HashSet<Long> allowed = new HashSet<Long>(); HashSet<Long> allowed = new HashSet<>();
allowed.add(mStaticRing2.getMasterKeyId()); allowed.add(mStaticRing2.getMasterKeyId());
// provide passphrase for the second, and check that the first is never asked for! // 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); mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
b.setAllowedKeyIds(allowed); 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.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -372,7 +370,7 @@ public class PgpEncryptDecryptTest {
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null); 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.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -395,7 +393,7 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), null); new ProviderHelper(Robolectric.application), null);
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput(); PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[] { b.setEncryptionMasterKeyIds(new long[] {
mStaticRing1.getMasterKeyId(), mStaticRing1.getMasterKeyId(),
@ -403,10 +401,9 @@ public class PgpEncryptDecryptTest {
}); });
b.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId()); b.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId());
b.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1)); b.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1));
b.setSignaturePassphrase(mKeyPhrase1);
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); 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()); Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray(); ciphertext = out.toByteArray();
@ -421,7 +418,7 @@ public class PgpEncryptDecryptTest {
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null); 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.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -447,7 +444,7 @@ public class PgpEncryptDecryptTest {
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out,
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null); 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.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext", Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes()); out.toByteArray(), plaintext.getBytes());
@ -477,14 +474,14 @@ public class PgpEncryptDecryptTest {
new ProviderHelper(Robolectric.application), null); new ProviderHelper(Robolectric.application), null);
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
PgpSignEncryptInput b = new PgpSignEncryptInput(); PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() }); b.setEncryptionMasterKeyIds(new long[]{ mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128); b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
// this only works with ascii armored output! // this only works with ascii armored output!
b.setEnableAsciiArmorOutput(true); b.setEnableAsciiArmorOutput(true);
b.setCharset("iso-2022-jp"); 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()); Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray(); ciphertext = out.toByteArray();
@ -497,8 +494,7 @@ public class PgpEncryptDecryptTest {
InputData data = new InputData(in, in.available()); InputData data = new InputData(in, in.available());
PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null); PgpDecryptVerify.Builder b = builderWithFakePassphraseCache(data, out, null, null, null);
b.setPassphrase(mKeyPhrase1); DecryptVerifyResult result = b.build().execute(new CryptoInputParcel(mKeyPhrase1));
DecryptVerifyResult result = b.build().execute();
Assert.assertTrue("decryption with provided passphrase must succeed", result.success()); Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes", Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes",
out.toByteArray(), plaindata); 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.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; 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.KeyringBuilder;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
@ -81,6 +82,8 @@ public class PgpKeyOperationTest {
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>(); ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>(); ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
static CryptoInputParcel cryptoInput;
@BeforeClass @BeforeClass
public static void setUpOnce() throws Exception { public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1); 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 // we sleep here for a second, to make sure all new certificates have different timestamps
Thread.sleep(1000); Thread.sleep(1000);
cryptoInput = new CryptoInputParcel(new Date(), passphrase);
} }
@Before public void setUp() throws Exception { @Before public void setUp() throws Exception {
@ -300,9 +306,16 @@ public class PgpKeyOperationTest {
if (badphrase.equals(passphrase)) { if (badphrase.equals(passphrase)) {
badphrase = new Passphrase("a"); badphrase = new Passphrase("a");
} }
parcel.mAddUserIds.add("allure");
assertModifyFailure("keyring modification with bad passphrase should fail", 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); parcel.mRevokeSubKeys.add(123L);
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); 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); Assert.assertNull("revoking a nonexistent subkey should fail", otherModified);
@ -657,7 +670,8 @@ public class PgpKeyOperationTest {
parcel.reset(); parcel.reset();
parcel.mRevokeSubKeys.add(keyId); 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("no extra packets in original", 0, onlyA.size());
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.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 { // we should be able to change the stripped/divert status of subkeys without passphrase
parcel.reset(); parcel.reset();
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null)); 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()); Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket(); Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
Assert.assertEquals("new packet should have GNU_DUMMY S2K type", 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}; };
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, false, serial)); 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()); Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket(); Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
Assert.assertEquals("new packet should have GNU_DUMMY S2K type", 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", Assert.assertEquals("signature type must be positive certification",
PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType()); PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
// make sure packets can be distinguished by timestamp
Thread.sleep(1000); Thread.sleep(1000);
// applying the same modification AGAIN should not add more certifications but drop those // applying the same modification AGAIN should not add more certifications but drop those
// as duplicates // 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 original", 1, onlyA.size());
Assert.assertEquals("duplicate modification: one extra packet in modified", 1, onlyB.size()); Assert.assertEquals("duplicate modification: one extra packet in modified", 1, onlyB.size());
@ -1039,8 +1053,7 @@ public class PgpKeyOperationTest {
// change passphrase to empty // change passphrase to empty
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase()); parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
// note that canonicalization here necessarily strips the empty notation packet // note that canonicalization here necessarily strips the empty notation packet
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput);
passphrase);
Assert.assertEquals("exactly three packets should have been modified (the secret keys)", Assert.assertEquals("exactly three packets should have been modified (the secret keys)",
3, onlyB.size()); 3, onlyB.size());
@ -1052,8 +1065,10 @@ public class PgpKeyOperationTest {
// modify keyring, change to non-empty passphrase // modify keyring, change to non-empty passphrase
Passphrase otherPassphrase = TestingUtils.genPassphrase(true); Passphrase otherPassphrase = TestingUtils.genPassphrase(true);
CryptoInputParcel otherCryptoInput = new CryptoInputParcel(otherPassphrase);
parcel.mNewUnlock = new ChangeUnlockParcel(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)", Assert.assertEquals("exactly three packets should have been modified (the secret keys)",
3, onlyB.size()); 3, onlyB.size());
@ -1086,7 +1101,7 @@ public class PgpKeyOperationTest {
// we should still be able to modify it (and change its passphrase) without errors // we should still be able to modify it (and change its passphrase) without errors
PgpKeyOperation op = new PgpKeyOperation(null); PgpKeyOperation op = new PgpKeyOperation(null);
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); 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.assertTrue("key modification must succeed", result.success());
Assert.assertFalse("log must not contain a warning", Assert.assertFalse("log must not contain a warning",
result.getLog().containsWarnings()); result.getLog().containsWarnings());
@ -1102,7 +1117,7 @@ public class PgpKeyOperationTest {
PgpKeyOperation op = new PgpKeyOperation(null); PgpKeyOperation op = new PgpKeyOperation(null);
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0); 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("key modification must succeed", result.success());
Assert.assertTrue("log must contain a failed passphrase change warning", Assert.assertTrue("log must contain a failed passphrase change warning",
result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_FAIL)); result.getLog().containsType(LogType.MSG_MF_PASSPHRASE_FAIL));
@ -1140,7 +1155,7 @@ public class PgpKeyOperationTest {
{ {
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase("phrayse"), null); 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)", Assert.assertEquals("exactly four packets should have been removed (the secret keys + notation packet)",
4, onlyA.size()); 4, onlyA.size());
@ -1157,7 +1172,7 @@ public class PgpKeyOperationTest {
parcel.mAddUserIds.add("discord"); parcel.mAddUserIds.add("discord");
PgpKeyOperation op = new PgpKeyOperation(null); 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()); Assert.assertFalse("non-restricted operations should fail without passphrase", result.success());
} }
@ -1165,15 +1180,15 @@ public class PgpKeyOperationTest {
UncachedKeyRing ring, UncachedKeyRing ring,
ArrayList<RawPacket> onlyA, ArrayList<RawPacket> onlyA,
ArrayList<RawPacket> onlyB) { 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, private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
UncachedKeyRing ring, UncachedKeyRing ring,
ArrayList<RawPacket> onlyA, ArrayList<RawPacket> onlyA,
ArrayList<RawPacket> onlyB, ArrayList<RawPacket> onlyB,
Passphrase passphrase) { CryptoInputParcel cryptoInput) {
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, passphrase, true, true); return applyModificationWithChecks(parcel, ring, onlyA, onlyB, cryptoInput, true, true);
} }
// applies a parcel modification while running some integrity checks // applies a parcel modification while running some integrity checks
@ -1181,7 +1196,7 @@ public class PgpKeyOperationTest {
UncachedKeyRing ring, UncachedKeyRing ring,
ArrayList<RawPacket> onlyA, ArrayList<RawPacket> onlyA,
ArrayList<RawPacket> onlyB, ArrayList<RawPacket> onlyB,
Passphrase passphrase, CryptoInputParcel cryptoInput,
boolean canonicalize, boolean canonicalize,
boolean constantCanonicalize) { boolean constantCanonicalize) {
@ -1191,7 +1206,7 @@ public class PgpKeyOperationTest {
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
PgpKeyOperation op = new PgpKeyOperation(null); 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()); Assert.assertTrue("key modification must succeed", result.success());
UncachedKeyRing rawModified = result.getRing(); UncachedKeyRing rawModified = result.getRing();
Assert.assertNotNull("key modification must not return null", rawModified); Assert.assertNotNull("key modification must not return null", rawModified);
@ -1258,11 +1273,11 @@ public class PgpKeyOperationTest {
} }
private void assertModifyFailure(String reason, UncachedKeyRing ring, private void assertModifyFailure(String reason, UncachedKeyRing ring,
SaveKeyringParcel parcel, Passphrase passphrase, LogType expected) SaveKeyringParcel parcel, CryptoInputParcel cryptoInput, LogType expected)
throws Exception { throws Exception {
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); 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.assertFalse(reason, result.success());
Assert.assertNull(reason, result.getRing()); Assert.assertNull(reason, result.getRing());
@ -1276,7 +1291,7 @@ public class PgpKeyOperationTest {
throws Exception { throws Exception {
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0); 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.assertFalse(reason, result.success());
Assert.assertNull(reason, result.getRing()); 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.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; 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;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
@ -549,7 +550,10 @@ public class UncachedKeyringCanonicalizeTest {
CanonicalizedSecretKey masterSecretKey = canonicalized.getSecretKey(); CanonicalizedSecretKey masterSecretKey = canonicalized.getSecretKey();
masterSecretKey.unlock(new Passphrase()); masterSecretKey.unlock(new Passphrase());
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
CryptoInputParcel cryptoInput = new CryptoInputParcel();
PGPSignature cert = PgpKeyOperation.generateSubkeyBindingSignature( PGPSignature cert = PgpKeyOperation.generateSubkeyBindingSignature(
PgpKeyOperation.getSignatureGenerator(masterSecretKey.getSecretKey(), cryptoInput),
cryptoInput.getSignatureTime(),
masterPublicKey, masterSecretKey.getPrivateKey(), masterSecretKey.getPrivateKey(), masterPublicKey, masterSecretKey.getPrivateKey(), masterSecretKey.getPrivateKey(),
masterPublicKey, masterSecretKey.getKeyUsage(), 0); masterPublicKey, masterSecretKey.getKeyUsage(), 0);
PGPPublicKey subPubKey = PGPPublicKey.addSubkeyBindingCertification(masterPublicKey, cert); 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.OperationResult;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; 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;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; 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;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
@ -46,6 +49,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.security.Security; import java.security.Security;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.Random; import java.util.Random;
@ -186,11 +190,11 @@ public class UncachedKeyringMergeTest {
parcel.reset(); parcel.reset();
parcel.mAddUserIds.add("flim"); 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.reset();
parcel.mAddUserIds.add("flam"); 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 { // merge A into base
@ -227,8 +231,8 @@ public class UncachedKeyringMergeTest {
parcel.reset(); parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd( parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L)); Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
modifiedA = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
modifiedB = op.modifySecretKeyRing(secretRing, parcel, new Passphrase()).getRing(); modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2); subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2);
subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2); subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2);
@ -269,7 +273,7 @@ public class UncachedKeyringMergeTest {
parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1)); parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1));
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
ringA.getEncoded(), false, 0); 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( CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing(
ringB.getEncoded(), false, 0).getSecretKey(); ringB.getEncoded(), false, 0).getSecretKey();
secretKey.unlock(new Passphrase()); secretKey.unlock(new Passphrase());
PgpCertifyOperation op = new PgpCertifyOperation();
CertifyAction action = new CertifyAction(pubRing.getMasterKeyId(), publicRing.getPublicKey().getUnorderedUserIds());
// sign all user ids // 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( CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
ringA.getEncoded(), false, 0); 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. // JCenter etc.
compile 'com.eftimoff:android-patternview:1.0.1@aar' compile 'com.eftimoff:android-patternview:1.0.1@aar'
compile 'com.journeyapps:zxing-android-embedded:2.1.0@aar' compile 'com.journeyapps:zxing-android-embedded:2.3.0@aar'
compile 'com.journeyapps:zxing-android-integration:2.1.0@aar' compile 'com.journeyapps:zxing-android-integration:2.3.0@aar'
compile 'com.google.zxing:core:3.2.0' compile 'com.google.zxing:core:3.2.0'
compile 'com.jpardogo.materialtabstrip:library:1.0.9' compile 'com.jpardogo.materialtabstrip:library:1.0.9'
compile 'it.neokree:MaterialNavigationDrawer:1.3.2' compile 'it.neokree:MaterialNavigationDrawer:1.3.2'
compile 'com.getbase:floatingactionbutton:1.9.0' compile 'com.getbase:floatingactionbutton:1.9.0'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final' 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 // libs as submodules
compile project(':extern:openpgp-api-lib') compile project(':extern:openpgp-api-lib')
compile project(':extern:openkeychain-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:core')
compile project(':extern:spongycastle:pg') compile project(':extern:spongycastle:pg')
compile project(':extern:spongycastle:pkix') compile project(':extern:spongycastle:pkix')
compile project(':extern:spongycastle:prov') compile project(':extern:spongycastle:prov')
compile project(':extern:minidns') compile project(':extern:minidns')
compile project(':extern:KeybaseLib:Lib') compile project(':extern:KeybaseLib:Lib')
compile project(':extern:TokenAutoComplete:library')
compile project(':extern:safeslinger-exchange') compile project(':extern:safeslinger-exchange')
compile project(':extern:snackbar:lib') compile project(':extern:snackbar:lib')
} }
@ -47,24 +47,24 @@ dependencyVerification {
'com.android.support:recyclerview-v7:859ed80e3761f8fc3126901260b208505120b5678bcf36ad2cfe9c453958b9c7', 'com.android.support:recyclerview-v7:859ed80e3761f8fc3126901260b208505120b5678bcf36ad2cfe9c453958b9c7',
'com.android.support:cardview-v7:4c03f2acce9925aa4f8845cb8cb37b3772c712b2438ff15f76c9e3d3bc63ead7', 'com.android.support:cardview-v7:4c03f2acce9925aa4f8845cb8cb37b3772c712b2438ff15f76c9e3d3bc63ead7',
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e', 'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
'com.journeyapps:zxing-android-embedded:57fdf8a262135976201fd89f1bd8016ed16510be92e7ea721b999daeeeab8f7e', 'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718',
'com.journeyapps:zxing-android-integration:12caeb2608f11b6df77d27edc505ac8580abfc97a09a814b638cb9df0ba06906', 'com.journeyapps:zxing-android-integration:562737821b6d34c899b6fd2234ce0a8a31e02ff1fd7c59f6211961ce9767c7c8',
'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b', 'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b',
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa', 'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
'it.neokree:MaterialNavigationDrawer:a1221a410c5f71bf078c5c4768fdf06b402d6006c74f8e7b61199e4edc2aea57', 'it.neokree:MaterialNavigationDrawer:a1221a410c5f71bf078c5c4768fdf06b402d6006c74f8e7b61199e4edc2aea57',
'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca', 'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca',
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13', '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:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580',
// 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429', // 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429',
// 'OpenKeychain.extern:html-textview:536822e8fdcd3e4628d0a1cd6c252285ba5f8e5bfb20d71ff80fdbdb6cc8be8c',
// 'OpenKeychain.extern.StickyListHeaders:library:d9937cf9d9992863e32cee1f18ffec12df7b97dd83939bb75ee6cf747c54bed1',
// 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b', // 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b',
// 'com.madgag.spongycastle:pg:160b345b10a2c92dc731453eec87037377f66a8e14a0648d404d7b193c4e380d', // 'com.madgag.spongycastle:pg:160b345b10a2c92dc731453eec87037377f66a8e14a0648d404d7b193c4e380d',
// 'com.madgag.spongycastle:pkix:0b4f3301ea12dd9f25d71770e6ea9f75e0611bf53062543e47be5bc15340a7e4', // 'com.madgag.spongycastle:pkix:0b4f3301ea12dd9f25d71770e6ea9f75e0611bf53062543e47be5bc15340a7e4',
// 'com.madgag.spongycastle:prov:7325942e0b39f5fb35d6380818eed4b826e7dfc7570ad35b696d778049d8c36a', // 'com.madgag.spongycastle:prov:7325942e0b39f5fb35d6380818eed4b826e7dfc7570ad35b696d778049d8c36a',
// 'OpenKeychain.extern:minidns:77b1786d29469e3b21f9404827cab811edc857cd68bc732cd57f11307c332eae', // 'OpenKeychain.extern:minidns:77b1786d29469e3b21f9404827cab811edc857cd68bc732cd57f11307c332eae',
// 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf', // 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf',
// 'OpenKeychain.extern.TokenAutoComplete:library:9333f1c269996812baa18c0494e42f931309ab00a3cdb65a6e4d70f82d4c7107',
// 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f', // 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f',
// 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23', // 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23',
'com.android.support:support-annotations:ab6b131ab0e1edd165d21fb4c3edadeacbee9539aa166f7f7cbae05b60dc207a', 'com.android.support:support-annotations:ab6b131ab0e1edd165d21fb4c3edadeacbee9539aa166f7f7cbae05b60dc207a',

View File

@ -90,6 +90,8 @@
android:name=".ui.CreateKeyActivity" android:name=".ui.CreateKeyActivity"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:label="@string/title_manage_my_keys" android:label="@string/title_manage_my_keys"
android:launchMode="singleTop"
android:allowTaskReparenting="true"
android:parentActivityName=".ui.MainActivity"> android:parentActivityName=".ui.MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@ -673,7 +675,7 @@
taskAffinity and allowTaskReparenting somehow prevents this from happening! taskAffinity and allowTaskReparenting somehow prevents this from happening!
--> -->
<activity <activity
android:name=".ui.NfcActivity" android:name=".ui.NfcOperationActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=":Nfc" android:taskAffinity=":Nfc"
android:allowTaskReparenting="true" /> android:allowTaskReparenting="true" />
@ -698,6 +700,10 @@
android:name=".service.PassphraseCacheService" android:name=".service.PassphraseCacheService"
android:exported="false" android:exported="false"
android:process=":passphrase_cache" /> android:process=":passphrase_cache" />
<service
android:name=".remote.CryptoInputParcelCacheService"
android:exported="false"
android:process=":remote_api" />
<service <service
android:name=".service.KeychainIntentService" android:name=".service.KeychainIntentService"
android:exported="false" /> android:exported="false" />

View File

@ -14,8 +14,12 @@ import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PGPDigestCalculator; import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.Provider; import java.security.Provider;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* This class is based on JcaPGPContentSignerBuilder. * This class is based on JcaPGPContentSignerBuilder.
@ -31,31 +35,27 @@ public class NfcSyncPGPContentSignerBuilder
private int keyAlgorithm; private int keyAlgorithm;
private long keyID; private long keyID;
private byte[] signedHash; private Map signedHashes;
private Date creationTimestamp;
public static class NfcInteractionNeeded extends RuntimeException public static class NfcInteractionNeeded extends RuntimeException
{ {
public byte[] hashToSign; public byte[] hashToSign;
public Date creationTimestamp;
public int hashAlgo; public int hashAlgo;
public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo, Date creationTimestamp) public NfcInteractionNeeded(byte[] hashToSign, int hashAlgo)
{ {
super("NFC interaction required!"); super("NFC interaction required!");
this.hashToSign = hashToSign; this.hashToSign = hashToSign;
this.hashAlgo = hashAlgo; 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.keyAlgorithm = keyAlgorithm;
this.hashAlgorithm = hashAlgorithm; this.hashAlgorithm = hashAlgorithm;
this.keyID = keyID; this.keyID = keyID;
this.signedHash = signedHash; this.signedHashes = signedHashes;
this.creationTimestamp = creationTimestamp;
} }
public NfcSyncPGPContentSignerBuilder setProvider(Provider provider) public NfcSyncPGPContentSignerBuilder setProvider(Provider provider)
@ -125,14 +125,14 @@ public class NfcSyncPGPContentSignerBuilder
} }
public byte[] getSignature() { public byte[] getSignature() {
if (signedHash != null) { byte[] digest = digestCalculator.getDigest();
// we already have the signed hash from a previous execution, return this! ByteBuffer buf = ByteBuffer.wrap(digest);
return signedHash; if (signedHashes.containsKey(buf)) {
} else { 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(digestCalculator.getDigest(), getHashAlgorithm(), creationTimestamp);
} }
// 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() 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.PGPDataDecryptor;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import java.nio.ByteBuffer;
import java.security.Provider; import java.security.Provider;
import java.util.Map;
/** /**
* This class is based on JcePublicKeyDataDecryptorFactoryBuilder * This class is based on JcePublicKeyDataDecryptorFactoryBuilder
@ -88,7 +91,7 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
return this; return this;
} }
public PublicKeyDataDecryptorFactory build(final byte[] nfcDecrypted) { public PublicKeyDataDecryptorFactory build(final Map<ByteBuffer,byte[]> nfcDecryptedMap) {
return new PublicKeyDataDecryptorFactory() return new PublicKeyDataDecryptorFactory()
{ {
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
@ -99,7 +102,7 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
throw new PGPException("ECDH not supported!"); 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) 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) private byte[] decryptSessionData(int keyAlgorithm, byte[][] secKeyData,
throws PGPException Map<ByteBuffer,byte[]> nfcDecryptedMap)
throws PGPException
{ {
// Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm); // Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm);
// //
@ -214,15 +218,14 @@ public class NfcSyncPublicKeyDataDecryptorFactoryBuilder
if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT
|| keyAlgorithm == PGPPublicKey.RSA_GENERAL) || keyAlgorithm == PGPPublicKey.RSA_GENERAL)
{ {
byte[] bi = secKeyData[0]; // encoded MPI ByteBuffer bi = ByteBuffer.wrap(secKeyData[0]); // encoded MPI
if (nfcDecrypted != null) { if (nfcDecryptedMap.containsKey(bi)) {
// we already have the decrypted bytes from a previous execution, return this! return nfcDecryptedMap.get(bi);
return nfcDecrypted;
} else { } else {
// catch this when decryptSessionData() is executed and divert digest to card, // catch this when decryptSessionData() is executed and divert digest to card,
// when doing the operation again reuse nfcDecrypted // when doing the operation again reuse nfcDecrypted
throw new NfcInteractionNeeded(bi); throw new NfcInteractionNeeded(bi.array());
} }
// c1.update(bi, 2, bi.length - 2); // 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.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; 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.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; 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;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; 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.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
@ -60,7 +64,7 @@ public class CertifyOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled); 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(); OperationLog log = new OperationLog();
log.add(LogType.MSG_CRT, 0); log.add(LogType.MSG_CRT, 0);
@ -74,13 +78,14 @@ public class CertifyOperation extends BaseOperation {
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId); mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
log.add(LogType.MSG_CRT_UNLOCK, 1); log.add(LogType.MSG_CRT_UNLOCK, 1);
certificationKey = secretKeyRing.getSecretKey(); certificationKey = secretKeyRing.getSecretKey();
if (certificationKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
log.add(LogType.MSG_CRT_ERROR_DIVERT, 2); if (!cryptoInput.hasPassphrase()) {
return new CertifyResult(CertifyResult.RESULT_ERROR, log); return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
} }
// certification is always with the master key id, so use that one // 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)) { if (!certificationKey.unlock(passphrase)) {
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2); log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
@ -92,9 +97,6 @@ public class CertifyOperation extends BaseOperation {
} catch (NotFoundException e) { } catch (NotFoundException e) {
log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2); log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
return new CertifyResult(CertifyResult.RESULT_ERROR, log); 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<>(); ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<>();
@ -103,6 +105,10 @@ public class CertifyOperation extends BaseOperation {
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0; int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
cryptoInput.getSignatureTime(), certificationKey.getKeyId(),
certificationKey.getKeyId());
// Work through all requested certifications // Work through all requested certifications
for (CertifyAction action : parcel.mCertifyActions) { for (CertifyAction action : parcel.mCertifyActions) {
@ -123,28 +129,21 @@ public class CertifyOperation extends BaseOperation {
CanonicalizedPublicKeyRing publicRing = CanonicalizedPublicKeyRing publicRing =
mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId); mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId);
UncachedKeyRing certifiedKey = null; PgpCertifyOperation op = new PgpCertifyOperation();
if (action.mUserIds != null) { PgpCertifyResult result = op.certify(certificationKey, publicRing,
log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(), log, 2, action, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
certifiedKey = certificationKey.certifyUserIds( if (!result.success()) {
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) {
certifyError += 1; 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) { } catch (NotFoundException e) {
certifyError += 1; 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); log.add(LogType.MSG_CRT_SAVING, 1);
// Check if we were cancelled // Check if we were cancelled

View File

@ -21,6 +21,7 @@ import android.content.Context;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult; 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.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; 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.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
@ -56,7 +58,7 @@ public class EditKeyOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled); super(context, providerHelper, progressable, cancelled);
} }
public EditKeyResult execute(SaveKeyringParcel saveParcel, Passphrase passphrase) { public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0); log.add(LogType.MSG_ED, 0);
@ -81,7 +83,10 @@ public class EditKeyOperation extends BaseOperation {
CanonicalizedSecretKeyRing secRing = CanonicalizedSecretKeyRing secRing =
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId); mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase); modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
if (modifyResult.isPending()) {
return modifyResult;
}
} catch (NotFoundException e) { } catch (NotFoundException e) {
log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2); 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) { } catch (Keyserver.QueryFailedException e) {
Log.e(Constants.TAG, "query failed", 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); super(context, providerHelper, progressable, cancelled);
} }
public PromoteKeyResult execute(long masterKeyId) { public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
log.add(LogType.MSG_PR, 0); log.add(LogType.MSG_PR, 0);
@ -58,27 +58,16 @@ public class PromoteKeyOperation extends BaseOperation {
// Perform actual type change // Perform actual type change
UncachedKeyRing promotedRing; UncachedKeyRing promotedRing;
{ {
try { 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, log.add(LogType.MSG_PR_FETCHING, 1,
KeyFormattingUtils.convertKeyIdToHex(masterKeyId)); KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
CanonicalizedPublicKeyRing pubRing = CanonicalizedPublicKeyRing pubRing =
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId); mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
// create divert-to-card secret key from public key // 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) { } catch (NotFoundException e) {
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2); log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null); 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.content.Context;
import android.net.Uri; import android.net.Uri;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.FileHelper;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
@ -55,7 +62,7 @@ public class SignEncryptOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled); super(context, providerHelper, progressable, cancelled);
} }
public SignEncryptResult execute(SignEncryptParcel input) { public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
log.add(LogType.MSG_SE, 0); log.add(LogType.MSG_SE, 0);
@ -68,6 +75,21 @@ public class SignEncryptOperation extends BaseOperation {
int total = inputBytes != null ? 1 : inputUris.size(), count = 0; int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
ArrayList<PgpSignEncryptResult> results = new ArrayList<>(); 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 { do {
if (checkCancelled()) { if (checkCancelled()) {
@ -123,15 +145,22 @@ public class SignEncryptOperation extends BaseOperation {
PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper, PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper,
new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled); 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); results.add(result);
log.add(result, 2); log.add(result, 2);
if (result.isPending()) { if (result.isPending()) {
return new SignEncryptResult(SignEncryptResult.RESULT_PENDING, log, results); RequiredInputParcel requiredInput = result.getRequiredInputParcel();
} // Passphrase returns immediately, nfc are aggregated
if (requiredInput.mType == RequiredInputType.PASSPHRASE) {
if (!result.success()) { 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); return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
} }
@ -141,9 +170,12 @@ public class SignEncryptOperation extends BaseOperation {
} while (!inputUris.isEmpty()); } while (!inputUris.isEmpty());
if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) {
return new SignEncryptResult(log, pendingInputBuilder.build(), results);
}
if (!outputUris.isEmpty()) { if (!outputUris.isEmpty()) {
// Any output URIs left are indicative of a programming error throw new AssertionError("Got outputs left but no inputs. This is a programming error, please report!");
log.add(LogType.MSG_SE_WARN_OUTPUT_LEFT, 1);
} }
log.add(LogType.MSG_SE_SUCCESS, 1); log.add(LogType.MSG_SE_SUCCESS, 1);

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Parcel; import android.os.Parcel;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity; import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment; import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; 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.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.Notify.Style;
public class CertifyResult extends OperationResult { public class CertifyResult extends InputPendingResult {
int mCertifyOk, mCertifyError, mUploadOk, mUploadError; int mCertifyOk, mCertifyError, mUploadOk, mUploadError;
public CertifyResult(int result, OperationLog log) { public CertifyResult(int result, OperationLog log) {
super(result, 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) { public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) {
this(result, log); super(result, log);
mCertifyOk = certifyOk; mCertifyOk = certifyOk;
mCertifyError = certifyError; mCertifyError = certifyError;
mUploadOk = uploadOk; mUploadOk = uploadOk;

View File

@ -22,23 +22,10 @@ import android.os.Parcel;
import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
public class DecryptVerifyResult extends OperationResult { public class DecryptVerifyResult 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_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;
OpenPgpSignatureResult mSignatureResult; OpenPgpSignatureResult mSignatureResult;
OpenPgpMetadata mDecryptMetadata; OpenPgpMetadata mDecryptMetadata;
@ -46,32 +33,6 @@ public class DecryptVerifyResult extends OperationResult {
// https://tools.ietf.org/html/rfc4880#page56 // https://tools.ietf.org/html/rfc4880#page56
String mCharset; 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() { public OpenPgpSignatureResult getSignatureResult() {
return mSignatureResult; return mSignatureResult;
} }
@ -104,13 +65,14 @@ public class DecryptVerifyResult extends OperationResult {
super(result, log); super(result, log);
} }
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
}
public DecryptVerifyResult(Parcel source) { public DecryptVerifyResult(Parcel source) {
super(source); super(source);
mKeyIdPassphraseNeeded = source.readLong();
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader()); mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader()); mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
mNfcSessionKey = source.readInt() != 0 ? source.createByteArray() : null;
mNfcPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
} }
public int describeContents() { public int describeContents() {
@ -119,16 +81,8 @@ public class DecryptVerifyResult extends OperationResult {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags); super.writeToParcel(dest, flags);
dest.writeLong(mKeyIdPassphraseNeeded);
dest.writeParcelable(mSignatureResult, 0); dest.writeParcelable(mSignatureResult, 0);
dest.writeParcelable(mDecryptMetadata, 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>() { public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {

View File

@ -31,13 +31,18 @@ public class EditKeyResult extends OperationResult {
public EditKeyResult(Parcel source) { public EditKeyResult(Parcel source) {
super(source); super(source);
mMasterKeyId = source.readLong(); mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, 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>() { 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.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableCache;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; 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 * 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 * to include typed additional information specific to the operation. To keep
* the class structure (somewhat) simple, this class contains an exhaustive * 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 * list (ie, enum) of all possible log types, which should in all cases be tied
* to string resource ids. * to string resource ids.
*
*/ */
public abstract class OperationResult implements Parcelable { public abstract class OperationResult implements Parcelable {
public static final String EXTRA_RESULT = "operation_result"; 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 * Instead of parceling the logs, they are cached to overcome the 1 MB boundary of
* to care about. This is used such that when we become parceled, we are * Android's Binder. See ParcelableCache
* well below the 1Mbit boundary that is specified.
*/ */
private static ConcurrentHashMap<UUID, OperationLog> dehydratedLogs; private static ParcelableCache<OperationLog> logCache;
static { static {
// Static initializer for ConcurrentHashMap logCache = new ParcelableCache<>();
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;
}
} }
/** Holds the overall result, the number specifying varying degrees of success: /** 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) { public OperationResult(Parcel source) {
mResult = source.readInt(); mResult = source.readInt();
long mostSig = source.readLong(); // get log out of cache based on UUID from source
long leastSig = source.readLong(); mLog = logCache.readFromParcelAndGetFromCache(source);
UUID mTicket = new UUID(mostSig, leastSig);
// fetch the dehydrated log out of storage (this removes it from the dehydration pool)
mLog = rehydrateLog(mTicket);
} }
public int getResult() { public int getResult() {
@ -250,12 +205,20 @@ public abstract class OperationResult implements Parcelable {
public Showable createNotify(final Activity activity) { 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 // 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; Style style;
@ -273,19 +236,19 @@ public abstract class OperationResult implements Parcelable {
} }
if (getLog() == null || getLog().isEmpty()) { 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, return Notify.create(activity, logText, Notify.LENGTH_LONG, style,
new ActionListener() { new ActionListener() {
@Override @Override
public void onAction() { public void onAction() {
Intent intent = new Intent( Intent intent = new Intent(
activity, LogDisplayActivity.class); activity, LogDisplayActivity.class);
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
activity.startActivity(intent); activity.startActivity(intent);
} }
}, R.string.view_log); }, R.string.view_log);
} }
@ -512,6 +475,7 @@ public abstract class OperationResult implements Parcelable {
// secret key modify // secret key modify
MSG_MF (LogLevel.START, R.string.msg_mr), 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_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_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint), 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_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_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_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_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_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), 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_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_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_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_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_ID (LogLevel.DEBUG, R.string.msg_mf_subkey_new_id),
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new), MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
@ -590,13 +558,11 @@ public abstract class OperationResult implements Parcelable {
// promote key // promote key
MSG_PR (LogLevel.START, R.string.msg_pr), 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_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_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success), MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
// messages used in UI code // 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_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy),
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found), 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_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_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_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), MSG_SE_SUCCESS (LogLevel.OK, R.string.msg_se_success),
// pgpsignencrypt // 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_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_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing),
MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock), 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 (LogLevel.START, R.string.msg_crt),
MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch), 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_SAVE (LogLevel.DEBUG, R.string.msg_crt_save),
MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving), MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving),
MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success), MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success),
@ -823,11 +788,8 @@ public abstract class OperationResult implements Parcelable {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mResult); dest.writeInt(mResult);
// Get a ticket for our log. // cache log and write UUID to dest
UUID mTicket = dehydrateLog(mLog); logCache.cacheAndWriteToParcel(mLog, dest);
// And write out the UUID most and least significant bits.
dest.writeLong(mTicket.getMostSignificantBits());
dest.writeLong(mTicket.getLeastSignificantBits());
} }
public static class OperationLog implements Iterable<LogEntryParcel> { 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.Constants;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; 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; private transient UncachedKeyRing mRing;
public final long mRingMasterKeyId; public final long mRingMasterKeyId;
@ -35,6 +37,11 @@ public class PgpEditKeyResult extends OperationResult {
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none; mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
} }
public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
mRingMasterKeyId = Constants.key.none;
}
public UncachedKeyRing getRing() { public UncachedKeyRing getRing() {
return mRing; return mRing;
} }

View File

@ -19,85 +19,31 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel; 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; 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) { public void setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = 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() { public byte[] getDetachedSignature() {
return mDetachedSignature; return mDetachedSignature;
} }
public boolean isPending() {
return (mResult & RESULT_PENDING) == RESULT_PENDING;
}
public PgpSignEncryptResult(int result, OperationLog log) { public PgpSignEncryptResult(int result, OperationLog log) {
super(result, log); super(result, log);
} }
public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
}
public PgpSignEncryptResult(Parcel source) { public PgpSignEncryptResult(Parcel source) {
super(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; mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
} }
@ -107,19 +53,6 @@ public class PgpSignEncryptResult extends OperationResult {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, 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) { if (mDetachedSignature != null) {
dest.writeInt(1); dest.writeInt(1);
dest.writeByteArray(mDetachedSignature); dest.writeByteArray(mDetachedSignature);

View File

@ -31,13 +31,18 @@ public class PromoteKeyResult extends OperationResult {
public PromoteKeyResult(Parcel source) { public PromoteKeyResult(Parcel source) {
super(source); super(source);
mMasterKeyId = source.readLong(); mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, 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>() { public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() {

View File

@ -21,20 +21,17 @@ import android.os.Parcel;
import java.util.ArrayList; import java.util.ArrayList;
public class SignEncryptResult extends OperationResult { import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class SignEncryptResult extends InputPendingResult {
ArrayList<PgpSignEncryptResult> mResults; ArrayList<PgpSignEncryptResult> mResults;
byte[] mResultBytes; byte[] mResultBytes;
public static final int RESULT_PENDING = RESULT_ERROR + 8; public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, ArrayList<PgpSignEncryptResult> results) {
super(log, requiredInput);
public PgpSignEncryptResult getPending() { mResults = results;
for (PgpSignEncryptResult sub : mResults) {
if (sub.isPending()) {
return sub;
}
}
return null;
} }
public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> 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 */ /** Create a dummy secret ring from this key */
public UncachedKeyRing createDummySecretRing () { public UncachedKeyRing createDummySecretRing () {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
return new UncachedKeyRing(secRing); 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.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; 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.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Wrapper for a PGPSecretKey. * Wrapper for a PGPSecretKey.
@ -184,13 +188,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return PgpConstants.sPreferredHashAlgorithms; return PgpConstants.sPreferredHashAlgorithms;
} }
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash, private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
Date nfcCreationTimestamp) { Map<ByteBuffer,byte[]> signedHashes) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
// use synchronous "NFC based" SignerBuilder // use synchronous "NFC based" SignerBuilder
return new NfcSyncPGPContentSignerBuilder( return new NfcSyncPGPContentSignerBuilder(
mSecretKey.getPublicKey().getAlgorithm(), hashAlgo, mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp) mSecretKey.getKeyID(), signedHashes)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
} else { } else {
// content signer based on signing key algorithm and chosen hash algorithm // 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, public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
byte[] nfcSignedHash, Date nfcCreationTimestamp) PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
throws PgpGeneralException { PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException(); 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. // We explicitly create a signature creation timestamp in this place.
// That way, we can inject an artificial one from outside, ie the one // That way, we can inject an artificial one from outside, ie the one
// used in previous runs of this function. // used in previous runs of this function.
if (nfcCreationTimestamp == null) { if (creationTimestamp == null) {
// to sign using nfc PgpSignEncrypt is executed two times. // 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 first time it stops to return the PendingIntent for nfc connection and signing the hash
// the second time the signed hash is used. // the second time the signed hash is used.
// to get the same hash we cache the timestamp for the second round! // to get the same hash we cache the timestamp for the second round!
nfcCreationTimestamp = new Date(); creationTimestamp = new Date();
} }
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, signedHashes);
nfcSignedHash, nfcCreationTimestamp);
int signatureType; int signatureType;
if (cleartext) { if (cleartext) {
@ -238,7 +256,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback()); spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
spGen.setSignatureCreationTime(false, nfcCreationTimestamp); spGen.setSignatureCreationTime(false, creationTimestamp);
signatureGenerator.setHashedSubpackets(spGen.generate()); signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator; return signatureGenerator;
} catch (PgpKeyNotFoundException | PGPException e) { } 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) { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException(); throw new PrivateKeyNotUnlockedException();
} }
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
return new NfcSyncPublicKeyDataDecryptorFactoryBuilder() return new NfcSyncPublicKeyDataDecryptorFactoryBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(nfcDecryptedSessionKey); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
cryptoInput.getCryptoData()
);
} else { } else {
return new JcePublicKeyDataDecryptorFactoryBuilder() return new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey);
} }
} }
/** public byte[] getIv() {
* Certify the given pubkeyid with the given masterkeyid. return mSecretKey.getIV();
*
* @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);
} }
static class PrivateKeyNotUnlockedException extends RuntimeException { static class PrivateKeyNotUnlockedException extends RuntimeException {
@ -402,4 +299,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return mPrivateKey; 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.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation; 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.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -84,10 +83,8 @@ public class PgpDecryptVerify extends BaseOperation {
private OutputStream mOutStream; private OutputStream mOutStream;
private boolean mAllowSymmetricDecryption; private boolean mAllowSymmetricDecryption;
private Passphrase mPassphrase;
private Set<Long> mAllowedKeyIds; private Set<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly; private boolean mDecryptMetadataOnly;
private byte[] mDecryptedSessionKey;
private byte[] mDetachedSignature; private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint; private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData; private boolean mSignedLiteralData;
@ -100,10 +97,8 @@ public class PgpDecryptVerify extends BaseOperation {
this.mOutStream = builder.mOutStream; this.mOutStream = builder.mOutStream;
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption; this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
this.mPassphrase = builder.mPassphrase;
this.mAllowedKeyIds = builder.mAllowedKeyIds; this.mAllowedKeyIds = builder.mAllowedKeyIds;
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly; this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
this.mDetachedSignature = builder.mDetachedSignature; this.mDetachedSignature = builder.mDetachedSignature;
this.mSignedLiteralData = builder.mSignedLiteralData; this.mSignedLiteralData = builder.mSignedLiteralData;
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint; this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
@ -119,10 +114,8 @@ public class PgpDecryptVerify extends BaseOperation {
private OutputStream mOutStream = null; private OutputStream mOutStream = null;
private Progressable mProgressable = null; private Progressable mProgressable = null;
private boolean mAllowSymmetricDecryption = true; private boolean mAllowSymmetricDecryption = true;
private Passphrase mPassphrase = null;
private Set<Long> mAllowedKeyIds = null; private Set<Long> mAllowedKeyIds = null;
private boolean mDecryptMetadataOnly = false; private boolean mDecryptMetadataOnly = false;
private byte[] mDecryptedSessionKey = null;
private byte[] mDetachedSignature = null; private byte[] mDetachedSignature = null;
private String mRequiredSignerFingerprint = null; private String mRequiredSignerFingerprint = null;
private boolean mSignedLiteralData = false; private boolean mSignedLiteralData = false;
@ -160,11 +153,6 @@ public class PgpDecryptVerify extends BaseOperation {
return this; return this;
} }
public Builder setPassphrase(Passphrase passphrase) {
mPassphrase = passphrase;
return this;
}
/** /**
* Allow these key ids alone for decryption. * Allow these key ids alone for decryption.
* This means only ciphertexts encrypted for one of these private key can be decrypted. * 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; return this;
} }
public Builder setNfcState(byte[] decryptedSessionKey) {
mDecryptedSessionKey = decryptedSessionKey;
return this;
}
/** /**
* If detachedSignature != null, it will be used exclusively to verify the signature * 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 * Decrypts and/or verifies data based on parameters of class
*/ */
public DecryptVerifyResult execute() { public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
try { try {
if (mDetachedSignature != null) { if (mDetachedSignature != null) {
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only"); 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); return verifyCleartextSignature(aIn, 0);
} else { } else {
// else: ascii armored encryption! go on... // else: ascii armored encryption! go on...
return decryptVerify(in, 0); return decryptVerify(cryptoInput, in, 0);
} }
} else { } else {
return decryptVerify(in, 0); return decryptVerify(cryptoInput, in, 0);
} }
} }
} catch (PGPException e) { } catch (PGPException e) {
@ -248,7 +231,8 @@ public class PgpDecryptVerify extends BaseOperation {
/** /**
* Verify Keybase.io style signed literal data * 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(); OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent); log.add(LogType.MSG_VL, indent);
@ -378,7 +362,8 @@ public class PgpDecryptVerify extends BaseOperation {
/** /**
* Decrypt and/or verifies binary or ascii armored pgp * 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(); 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 // go through all objects and find one we can decrypt
while (it.hasNext()) { while (it.hasNext()) {
Object obj = it.next(); Object obj = it.next();
@ -492,11 +479,15 @@ public class PgpDecryptVerify extends BaseOperation {
encryptedDataAsymmetric = encData; encryptedDataAsymmetric = encData;
// if no passphrase was explicitly set try to get it from the cache service if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
if (mPassphrase == null) { 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 { try {
// returns "" if key has no passphrase // returns "" if key has no passphrase
mPassphrase = getCachedPassphrase(subKeyId); passphrase = getCachedPassphrase(subKeyId);
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1); log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
} catch (PassphraseCacheInterface.NoSecretKeyException e) { } catch (PassphraseCacheInterface.NoSecretKeyException e) {
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1); 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 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); log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
DecryptVerifyResult result = return new DecryptVerifyResult(log,
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log); RequiredInputParcel.createRequiredDecryptPassphrase(
result.setKeyIdPassphraseNeeded(subKeyId); secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
return result;
} }
} }
@ -536,11 +526,14 @@ public class PgpDecryptVerify extends BaseOperation {
// if no passphrase is given, return here // if no passphrase is given, return here
// indicating that a passphrase is missing! // indicating that a passphrase is missing!
if (mPassphrase == null) { if (!cryptoInput.hasPassphrase()) {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1); 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 out of while, only decrypt the first packet
break; break;
} }
@ -573,7 +566,7 @@ public class PgpDecryptVerify extends BaseOperation {
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder( PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
mPassphrase.getCharArray()); passphrase.getCharArray());
clear = encryptedDataSymmetric.getDataStream(decryptorFactory); clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
encryptedData = encryptedDataSymmetric; encryptedData = encryptedDataSymmetric;
@ -585,7 +578,7 @@ public class PgpDecryptVerify extends BaseOperation {
try { try {
log.add(LogType.MSG_DC_UNLOCKING, indent + 1); 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); log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
} }
@ -599,16 +592,15 @@ public class PgpDecryptVerify extends BaseOperation {
try { try {
PublicKeyDataDecryptorFactory decryptorFactory PublicKeyDataDecryptorFactory decryptorFactory
= secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey); = secretEncryptionKey.getDecryptorFactory(cryptoInput);
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory); clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory); symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) { } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1); log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
DecryptVerifyResult result = return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log); e.encryptedSessionKey, secretEncryptionKey.getKeyId()
result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase); ));
return result;
} }
encryptedData = encryptedDataAsymmetric; encryptedData = encryptedDataAsymmetric;
} else { } else {
@ -878,8 +870,8 @@ public class PgpDecryptVerify extends BaseOperation {
* The method is heavily based on * The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java * pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/ */
private DecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn, int indent) private DecryptVerifyResult verifyCleartextSignature(
throws IOException, PGPException { ArmoredInputStream aIn, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();

View File

@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.pgp; 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.Features;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec; 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.JcaPGPKeyPair;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; 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.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult; 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.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; 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.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -86,6 +91,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* This indicator may be null. * This indicator may be null.
*/ */
public class PgpKeyOperation { public class PgpKeyOperation {
private Stack<Progressable> mProgress; private Stack<Progressable> mProgress;
private AtomicBoolean mCancelled; private AtomicBoolean mCancelled;
@ -317,7 +323,8 @@ public class PgpKeyOperation {
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator()); masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
subProgressPush(50, 100); 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) { } catch (PGPException e) {
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent); 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. * namely stripping of subkeys and changing the protection mode of dummy keys.
* *
*/ */
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel, public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
Passphrase passphrase) { CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel) {
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
int indent = 0; int indent = 0;
@ -387,11 +395,24 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} }
// If we have no passphrase, only allow restricted operation if (saveParcel.isEmpty()) {
if (passphrase == null) { 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); 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. // read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER // since this is the master key, this contains at least CERTIFY_OTHER
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
@ -399,33 +420,45 @@ public class PgpKeyOperation {
Date expiryTime = wsKR.getPublicKey().getExpiryTime(); Date expiryTime = wsKR.getPublicKey().getExpiryTime();
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L; 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, private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
int masterKeyFlags, long masterKeyExpiry, int masterKeyFlags, long masterKeyExpiry,
SaveKeyringParcel saveParcel, Passphrase passphrase, CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel,
OperationLog log) { OperationLog log) {
int indent = 1; int indent = 1;
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
masterSecretKey.getKeyID());
progress(R.string.progress_modify, 0); progress(R.string.progress_modify, 0);
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey(); PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
// 1. Unlock private key
progress(R.string.progress_modify_unlock, 10);
log.add(LogType.MSG_MF_UNLOCK, indent);
PGPPrivateKey masterPrivateKey; PGPPrivateKey masterPrivateKey;
{
try { if (isDivertToCard(masterSecretKey)) {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( masterPrivateKey = null;
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray()); log.add(LogType.MSG_MF_DIVERT, indent);
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); } else {
} catch (PGPException e) {
log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1); // 1. Unlock private key
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); 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); String userId = saveParcel.mAddUserIds.get(i);
log.add(LogType.MSG_MF_UID_ADD, indent, userId); 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); log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} }
@ -480,9 +513,16 @@ public class PgpKeyOperation {
boolean isPrimary = saveParcel.mChangePrimaryUserId != null boolean isPrimary = saveParcel.mChangePrimaryUserId != null
&& userId.equals(saveParcel.mChangePrimaryUserId); && userId.equals(saveParcel.mChangePrimaryUserId);
// generate and add new certificate // generate and add new certificate
PGPSignature cert = generateUserIdSignature(masterPrivateKey, try {
masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry); PGPSignature cert = generateUserIdSignature(
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); 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(); subProgressPop();
@ -509,9 +549,15 @@ public class PgpKeyOperation {
PGPUserAttributeSubpacketVector vector = attribute.getVector(); PGPUserAttributeSubpacketVector vector = attribute.getVector();
// generate and add new certificate // generate and add new certificate
PGPSignature cert = generateUserAttributeSignature(masterPrivateKey, try {
masterPublicKey, vector); PGPSignature cert = generateUserAttributeSignature(
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert); getSignatureGenerator(masterSecretKey, cryptoInput),
cryptoInput.getSignatureTime(),
masterPrivateKey, masterPublicKey, vector);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
}
} }
subProgressPop(); subProgressPop();
@ -539,9 +585,15 @@ public class PgpKeyOperation {
// a duplicate revocation will be removed during canonicalization, so no need to // a duplicate revocation will be removed during canonicalization, so no need to
// take care of that here. // take care of that here.
PGPSignature cert = generateRevocationSignature(masterPrivateKey, try {
masterPublicKey, userId); PGPSignature cert = generateRevocationSignature(
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert); getSignatureGenerator(masterSecretKey, cryptoInput),
cryptoInput.getSignatureTime(),
masterPrivateKey, masterPublicKey, userId);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
}
} }
subProgressPop(); subProgressPop();
@ -611,11 +663,18 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent); log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert); modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature( try {
masterPrivateKey, masterPublicKey, userId, false, PGPSignature newCert = generateUserIdSignature(
masterKeyFlags, masterKeyExpiry); getSignatureGenerator(masterSecretKey, cryptoInput),
modifiedPublicKey = PGPPublicKey.addCertification( cryptoInput.getSignatureTime(),
modifiedPublicKey, userId, newCert); masterPrivateKey, masterPublicKey, userId, false,
masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
}
continue; continue;
} }
@ -627,11 +686,17 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_PRIMARY_NEW, indent); log.add(LogType.MSG_MF_PRIMARY_NEW, indent);
modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert); modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature( try {
masterPrivateKey, masterPublicKey, userId, true, PGPSignature newCert = generateUserIdSignature(
masterKeyFlags, masterKeyExpiry); getSignatureGenerator(masterSecretKey, cryptoInput),
modifiedPublicKey = PGPPublicKey.addCertification( cryptoInput.getSignatureTime(),
modifiedPublicKey, userId, newCert); masterPrivateKey, masterPublicKey, userId, true,
masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification(
modifiedPublicKey, userId, newCert);
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
}
ok = true; ok = true;
} }
@ -718,8 +783,9 @@ public class PgpKeyOperation {
} }
PGPPublicKey pKey = PGPPublicKey pKey =
updateMasterCertificates(masterPrivateKey, masterPublicKey, updateMasterCertificates(
flags, expiry, indent, log); masterSecretKey, masterPrivateKey, masterPublicKey,
flags, expiry, cryptoInput, nfcSignOps, indent, log);
if (pKey == null) { if (pKey == null) {
// error log entry has already been added by updateMasterCertificates itself // error log entry has already been added by updateMasterCertificates itself
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@ -756,9 +822,16 @@ public class PgpKeyOperation {
pKey = PGPPublicKey.removeCertification(pKey, sig); 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 // generate and add new signature
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, flags, expiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig); pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
} }
@ -782,10 +855,17 @@ public class PgpKeyOperation {
PGPPublicKey pKey = sKey.getPublicKey(); PGPPublicKey pKey = sKey.getPublicKey();
// generate and add new signature // 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); pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
}
} }
subProgressPop(); subProgressPop();
@ -828,10 +908,16 @@ public class PgpKeyOperation {
// add subkey binding signature (making this a sub rather than master key) // add subkey binding signature (making this a sub rather than master key)
PGPPublicKey pKey = keyPair.getPublicKey(); PGPPublicKey pKey = keyPair.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature( try {
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey, PGPSignature cert = generateSubkeyBindingSignature(
add.mFlags, add.mExpiry); getSignatureGenerator(masterSecretKey, cryptoInput),
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert); 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; { PGPSecretKey sKey; {
// Build key encrypter and decrypter based on passphrase // Build key encrypter and decrypter based on passphrase
@ -840,7 +926,8 @@ public class PgpKeyOperation {
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) 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() PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO); .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
@ -868,7 +955,7 @@ public class PgpKeyOperation {
indent += 1; indent += 1;
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey, sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
passphrase, saveParcel.mNewUnlock, log, indent); cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent);
if (sKR == null) { if (sKR == null) {
// The error has been logged above, just return a bad state // The error has been logged above, just return a bad state
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null); return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@ -892,6 +979,12 @@ public class PgpKeyOperation {
} }
progress(R.string.progress_done, 100); 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); log.add(LogType.MSG_MF_SUCCESS, indent);
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR)); return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
@ -1064,8 +1157,7 @@ public class PgpKeyOperation {
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT) PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
newPassphrase.getCharArray());
// noinspection unchecked // noinspection unchecked
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) { 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. */ /** Update all (non-revoked) uid signatures with new flags and expiry time. */
private static PGPPublicKey updateMasterCertificates( private PGPPublicKey updateMasterCertificates(
PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey,
int flags, long expiry, int indent, OperationLog log) PGPPublicKey masterPublicKey,
int flags, long expiry,
CryptoInputParcel cryptoInput,
NfcSignOperationsBuilder nfcSignOps,
int indent, OperationLog log)
throws PGPException, IOException, SignatureException { throws PGPException, IOException, SignatureException {
// keep track if we actually changed one // keep track if we actually changed one
@ -1173,10 +1269,16 @@ public class PgpKeyOperation {
currentCert.getHashedSubPackets().isPrimaryUserID(); currentCert.getHashedSubPackets().isPrimaryUserID();
modifiedPublicKey = PGPPublicKey.removeCertification( modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert); modifiedPublicKey, userId, currentCert);
PGPSignature newCert = generateUserIdSignature( try {
masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry); PGPSignature newCert = generateUserIdSignature(
modifiedPublicKey = PGPPublicKey.addCertification( getSignatureGenerator(masterSecretKey, cryptoInput),
modifiedPublicKey, userId, newCert); 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; 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, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
int flags, long expiry) int flags, long expiry)
throws IOException, PGPException, SignatureException { 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(); PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{ {
@ -1223,7 +1347,7 @@ public class PgpKeyOperation {
hashedPacketsGen.setPrimaryUserID(false, primary); hashedPacketsGen.setPrimaryUserID(false, primary);
/* critical subpackets: we consider those important for a modern pgp implementation */ /* 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) // Request that senders add the MDC to the message (authenticate unsigned messages)
hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION); hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
hashedPacketsGen.setKeyFlags(true, flags); hashedPacketsGen.setKeyFlags(true, flags);
@ -1239,19 +1363,15 @@ public class PgpKeyOperation {
} }
private static PGPSignature generateUserAttributeSignature( private static PGPSignature generateUserAttributeSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
PGPUserAttributeSubpacketVector vector) PGPUserAttributeSubpacketVector vector)
throws IOException, PGPException, SignatureException { 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(); PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{ {
/* critical subpackets: we consider those important for a modern pgp implementation */ /* critical subpackets: we consider those important for a modern pgp implementation */
hashedPacketsGen.setSignatureCreationTime(true, new Date()); hashedPacketsGen.setSignatureCreationTime(true, creationTime);
} }
sGen.setHashedSubpackets(hashedPacketsGen.generate()); sGen.setHashedSubpackets(hashedPacketsGen.generate());
@ -1260,29 +1380,24 @@ public class PgpKeyOperation {
} }
private static PGPSignature generateRevocationSignature( private static PGPSignature generateRevocationSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId) PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
throws IOException, PGPException, SignatureException { 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(); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(true, new Date()); subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey); sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey); return sGen.generateCertification(userId, pKey);
} }
private static PGPSignature generateRevocationSignature( private static PGPSignature generateRevocationSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey) PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey)
throws IOException, PGPException, SignatureException { 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(); PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(true, new Date()); subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); sGen.setHashedSubpackets(subHashedPacketsGen.generate());
// Generate key revocation or subkey revocation, depending on master/subkey-ness // Generate key revocation or subkey revocation, depending on master/subkey-ness
if (masterPublicKey.getKeyID() == pKey.getKeyID()) { 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( static PGPSignature generateSubkeyBindingSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry) PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
throws IOException, PGPException, SignatureException { throws IOException, PGPException, SignatureException {
// date for signing
Date creationTime = new Date();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
// If this key can sign, we need a primary key binding signature // If this key can sign, we need a primary key binding signature
@ -1324,10 +1425,10 @@ public class PgpKeyOperation {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder( PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder); PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey); subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
sGen.setHashedSubpackets(subHashedPacketsGen.generate()); subSigGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey); PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey);
unhashedPacketsGen.setEmbeddedSignature(true, certification); 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.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey);
sGen.setHashedSubpackets(hashedPacketsGen.generate()); sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate()); sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
@ -1372,4 +1469,16 @@ public class PgpKeyOperation {
return flags; 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.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import java.nio.ByteBuffer;
import java.util.Date; 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 String mVersionHeader = null;
protected boolean mEnableAsciiArmorOutput = false; protected boolean mEnableAsciiArmorOutput = false;
@ -35,16 +42,68 @@ public class PgpSignEncryptInput {
protected long mSignatureMasterKeyId = Constants.key.none; protected long mSignatureMasterKeyId = Constants.key.none;
protected Long mSignatureSubKeyId = null; protected Long mSignatureSubKeyId = null;
protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED; protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
protected Passphrase mSignaturePassphrase = null;
protected long mAdditionalEncryptId = Constants.key.none; protected long mAdditionalEncryptId = Constants.key.none;
protected byte[] mNfcSignedHash = null;
protected Date mNfcCreationTimestamp = null;
protected boolean mFailOnMissingEncryptionKeyIds = false; protected boolean mFailOnMissingEncryptionKeyIds = false;
protected String mCharset; protected String mCharset;
protected boolean mCleartextSignature; protected boolean mCleartextSignature;
protected boolean mDetachedSignature = false; protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = 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() { public String getCharset() {
return mCharset; return mCharset;
} }
@ -57,37 +116,20 @@ public class PgpSignEncryptInput {
return mFailOnMissingEncryptionKeyIds; return mFailOnMissingEncryptionKeyIds;
} }
public Date getNfcCreationTimestamp() {
return mNfcCreationTimestamp;
}
public byte[] getNfcSignedHash() {
return mNfcSignedHash;
}
public long getAdditionalEncryptId() { public long getAdditionalEncryptId() {
return mAdditionalEncryptId; return mAdditionalEncryptId;
} }
public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) { public PgpSignEncryptInputParcel setAdditionalEncryptId(long additionalEncryptId) {
mAdditionalEncryptId = additionalEncryptId; mAdditionalEncryptId = additionalEncryptId;
return this; return this;
} }
public Passphrase getSignaturePassphrase() {
return mSignaturePassphrase;
}
public PgpSignEncryptInput setSignaturePassphrase(Passphrase signaturePassphrase) {
mSignaturePassphrase = signaturePassphrase;
return this;
}
public int getSignatureHashAlgorithm() { public int getSignatureHashAlgorithm() {
return mSignatureHashAlgorithm; return mSignatureHashAlgorithm;
} }
public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) { public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) {
mSignatureHashAlgorithm = signatureHashAlgorithm; mSignatureHashAlgorithm = signatureHashAlgorithm;
return this; return this;
} }
@ -96,7 +138,7 @@ public class PgpSignEncryptInput {
return mSignatureSubKeyId; return mSignatureSubKeyId;
} }
public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) { public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) {
mSignatureSubKeyId = signatureSubKeyId; mSignatureSubKeyId = signatureSubKeyId;
return this; return this;
} }
@ -105,7 +147,7 @@ public class PgpSignEncryptInput {
return mSignatureMasterKeyId; return mSignatureMasterKeyId;
} }
public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) { public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) {
mSignatureMasterKeyId = signatureMasterKeyId; mSignatureMasterKeyId = signatureMasterKeyId;
return this; return this;
} }
@ -114,7 +156,7 @@ public class PgpSignEncryptInput {
return mSymmetricEncryptionAlgorithm; return mSymmetricEncryptionAlgorithm;
} }
public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) { public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm; mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
return this; return this;
} }
@ -123,7 +165,7 @@ public class PgpSignEncryptInput {
return mSymmetricPassphrase; return mSymmetricPassphrase;
} }
public PgpSignEncryptInput setSymmetricPassphrase(Passphrase symmetricPassphrase) { public PgpSignEncryptInputParcel setSymmetricPassphrase(Passphrase symmetricPassphrase) {
mSymmetricPassphrase = symmetricPassphrase; mSymmetricPassphrase = symmetricPassphrase;
return this; return this;
} }
@ -132,7 +174,7 @@ public class PgpSignEncryptInput {
return mEncryptionMasterKeyIds; return mEncryptionMasterKeyIds;
} }
public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) { public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
mEncryptionMasterKeyIds = encryptionMasterKeyIds; mEncryptionMasterKeyIds = encryptionMasterKeyIds;
return this; return this;
} }
@ -141,7 +183,7 @@ public class PgpSignEncryptInput {
return mCompressionId; return mCompressionId;
} }
public PgpSignEncryptInput setCompressionId(int compressionId) { public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
mCompressionId = compressionId; mCompressionId = compressionId;
return this; return this;
} }
@ -154,28 +196,22 @@ public class PgpSignEncryptInput {
return mVersionHeader; return mVersionHeader;
} }
public PgpSignEncryptInput setVersionHeader(String versionHeader) { public PgpSignEncryptInputParcel setVersionHeader(String versionHeader) {
mVersionHeader = versionHeader; mVersionHeader = versionHeader;
return this; return this;
} }
public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) { public PgpSignEncryptInputParcel setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
mEnableAsciiArmorOutput = enableAsciiArmorOutput; mEnableAsciiArmorOutput = enableAsciiArmorOutput;
return this; return this;
} }
public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) { public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds; mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
return this; return this;
} }
public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) { public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
mNfcSignedHash = signedHash;
mNfcCreationTimestamp = creationTimestamp;
return this;
}
public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) {
this.mCleartextSignature = cleartextSignature; this.mCleartextSignature = cleartextSignature;
return this; return this;
} }
@ -184,7 +220,7 @@ public class PgpSignEncryptInput {
return mCleartextSignature; return mCleartextSignature;
} }
public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) { public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) {
this.mDetachedSignature = detachedSignature; this.mDetachedSignature = detachedSignature;
return this; return this;
} }
@ -193,7 +229,7 @@ public class PgpSignEncryptInput {
return mDetachedSignature; return mDetachedSignature;
} }
public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) { public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) {
this.mHiddenRecipients = hiddenRecipients; this.mHiddenRecipients = hiddenRecipients;
return this; return this;
} }
@ -201,5 +237,16 @@ public class PgpSignEncryptInput {
public boolean isHiddenRecipients() { public boolean isHiddenRecipients() {
return mHiddenRecipients; 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.JcePBEKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation; 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.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler; import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.BufferedOutputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -72,7 +75,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* <p/> * <p/>
* For a high-level operation based on URIs, see SignEncryptOperation. * 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.results.PgpSignEncryptResult
* @see org.sufficientlysecure.keychain.operations.SignEncryptOperation * @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 * Signs and/or encrypts data based on parameters of class
*/ */
public PgpSignEncryptResult execute(PgpSignEncryptInput input, public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) { InputData inputData, OutputStream outputStream) {
int indent = 0; int indent = 0;
OperationLog log = new OperationLog(); OperationLog log = new OperationLog();
@ -128,7 +131,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
ArmoredOutputStream armorOut = null; ArmoredOutputStream armorOut = null;
OutputStream out; OutputStream out;
if (input.isEnableAsciiArmorOutput()) { if (input.isEnableAsciiArmorOutput()) {
armorOut = new ArmoredOutputStream(outputStream); armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
if (input.getVersionHeader() != null) { if (input.getVersionHeader() != null) {
armorOut.setHeader("Version", input.getVersionHeader()); armorOut.setHeader("Version", input.getVersionHeader());
} }
@ -145,62 +148,62 @@ public class PgpSignEncryptOperation extends BaseOperation {
CanonicalizedSecretKey signingKey = null; CanonicalizedSecretKey signingKey = null;
if (enableSignature) { if (enableSignature) {
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
try { try {
// fetch the indicated master key id (the one whose name we sign in) // fetch the indicated master key id (the one whose name we sign in)
CanonicalizedSecretKeyRing signingKeyRing = CanonicalizedSecretKeyRing signingKeyRing =
mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId()); mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
long signKeyId; // fetch the specific subkey to sign with, or just use the master key if none specified
// use specified signing subkey, or find the one to use signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
if (input.getSignatureSubKeyId() == null) {
signKeyId = signingKeyRing.getSecretSignId(); // Make sure we are allowed to sign here!
} else { if (!signingKey.canSign()) {
signKeyId = input.getSignatureSubKeyId(); 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 switch (signingKey.getSecretKeyType()) {
signingKey = signingKeyRing.getSecretKey(signKeyId); 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); log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); 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) { } catch (PgpGeneralException e) {
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent); log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
@ -281,8 +284,9 @@ public class PgpSignEncryptOperation extends BaseOperation {
try { try {
boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption; boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
signatureGenerator = signingKey.getSignatureGenerator( signatureGenerator = signingKey.getDataSignatureGenerator(
input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp()); input.getSignatureHashAlgorithm(), cleartext,
cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
log.add(LogType.MSG_PSE_ERROR_NFC, indent); log.add(LogType.MSG_PSE_ERROR_NFC, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
@ -405,7 +409,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
detachedByteOut = new ByteArrayOutputStream(); detachedByteOut = new ByteArrayOutputStream();
OutputStream detachedOut = detachedByteOut; OutputStream detachedOut = detachedByteOut;
if (input.isEnableAsciiArmorOutput()) { if (input.isEnableAsciiArmorOutput()) {
detachedArmorOut = new ArmoredOutputStream(detachedOut); detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
if (input.getVersionHeader() != null) { if (input.getVersionHeader() != null) {
detachedArmorOut.setHeader("Version", input.getVersionHeader()); detachedArmorOut.setHeader("Version", input.getVersionHeader());
} }
@ -485,19 +489,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
log.add(LogType.MSG_PSE_PENDING_NFC, indent); log.add(LogType.MSG_PSE_PENDING_NFC, indent);
PgpSignEncryptResult result = return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log); e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
// 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;
} }
} }

View File

@ -20,14 +20,10 @@ package org.sufficientlysecure.keychain.pgp;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
/** This parcel stores the input of one or more PgpSignEncrypt operations. /** 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. * 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> mInputUris = new ArrayList<>();
public ArrayList<Uri> mOutputUris = new ArrayList<>(); public ArrayList<Uri> mOutputUris = new ArrayList<>();
@ -53,26 +49,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
} }
public SignEncryptParcel(Parcel src) { public SignEncryptParcel(Parcel src) {
super(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;
mInputUris = src.createTypedArrayList(Uri.CREATOR); mInputUris = src.createTypedArrayList(Uri.CREATOR);
mOutputUris = 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) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mVersionHeader); super.writeToParcel(dest, flags);
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);
dest.writeTypedList(mInputUris); dest.writeTypedList(mInputUris);
dest.writeTypedList(mOutputUris); 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) { if (modified != resultKey) {
result = replacePublicKey(result, modified); 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; package org.sufficientlysecure.keychain.remote;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@ -31,16 +32,15 @@ import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; 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.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;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; 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.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity; 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.ImportKeysActivity;
import org.sufficientlysecure.keychain.ui.NfcActivity; import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
@ -60,7 +62,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.Set; import java.util.Set;
public class OpenPgpService extends RemoteService { public class OpenPgpService extends RemoteService {
@ -78,9 +79,6 @@ public class OpenPgpService extends RemoteService {
/** /**
* Search database for key ids based on emails. * Search database for key ids based on emails.
*
* @param encryptionUserIds
* @return
*/ */
private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0); boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
@ -163,52 +161,35 @@ public class OpenPgpService extends RemoteService {
} }
} }
private Intent returnPassphraseIntent(Intent data, long keyId) { private static PendingIntent getRequiredInputPendingIntent(Context context,
// build PendingIntent for passphrase input Intent data, RequiredInputParcel requiredInput) {
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);
// return PendingIntent to be executed by client switch (requiredInput.mType) {
Intent result = new Intent(); case NFC_DECRYPT:
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); case NFC_SIGN: {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); // build PendingIntent for YubiKey NFC operations
return result; 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) { case PASSPHRASE: {
// build PendingIntent for Yubikey NFC operations // build PendingIntent for Passphrase request
Intent intent = new Intent(getBaseContext(), NfcActivity.class); Intent intent = new Intent(context, PassphraseDialogActivity.class);
intent.setAction(NfcActivity.ACTION_SIGN_HASH); // pass params through to activity that it can be returned again later to repeat pgp operation
// 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(NfcActivity.EXTRA_DATA, data); intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
intent.putExtra(NfcActivity.EXTRA_PIN, pin); return PendingIntent.getActivity(context, 0, intent,
intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId); PendingIntent.FLAG_CANCEL_CURRENT);
}
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign); default:
intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo); throw new AssertionError("Unhandled required input type!");
return PendingIntent.getActivity(getBaseContext(), 0, }
intent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
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) { private PendingIntent getKeyserverPendingIntent(Intent data, long masterKeyId) {
@ -240,17 +221,13 @@ public class OpenPgpService extends RemoteService {
try { try {
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
Passphrase passphrase = null; // sign-only
if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) { PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel()
passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)); .setEnableAsciiArmorOutput(asciiArmor)
} .setCleartextSignature(cleartextSign)
.setDetachedSignature(!cleartextSign)
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH); .setVersionHeader(null)
if (nfcSignedHash != null) { .setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
Log.d(Constants.TAG, "nfcSignedHash:" + Hex.toHexString(nfcSignedHash));
} else {
Log.d(Constants.TAG, "nfcSignedHash: null");
}
Intent signKeyIdIntent = getSignKeyMasterId(data); Intent signKeyIdIntent = getSignKeyMasterId(data);
// NOTE: Fallback to return account settings (Old API) // NOTE: Fallback to return account settings (Old API)
@ -258,17 +235,21 @@ public class OpenPgpService extends RemoteService {
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) { == OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
return signKeyIdIntent; return signKeyIdIntent;
} }
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none); long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
if (signKeyId == 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 // get first usable subkey capable of signing
Date nfcCreationDate = null; try {
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1); long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
Log.d(Constants.TAG, "nfcCreationTimestamp: " + nfcCreationTimestamp); pseInput.getSignatureMasterKeyId()).getSecretSignId();
if (nfcCreationTimestamp != -1) { pseInput.setSignatureSubKeyId(signSubKeyId);
nfcCreationDate = new Date(nfcCreationTimestamp); } catch (PgpKeyNotFoundException e) {
throw new Exception("signing subkey not found!", e);
}
} }
// Get Input- and OutputStream from ParcelFileDescriptor // Get Input- and OutputStream from ParcelFileDescriptor
@ -281,42 +262,31 @@ public class OpenPgpService extends RemoteService {
long inputLength = is.available(); long inputLength = is.available();
InputData inputData = new InputData(is, inputLength); InputData inputData = new InputData(is, inputLength);
// sign-only CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
PgpSignEncryptInput pseInput = new PgpSignEncryptInput() if (inputParcel == null) {
.setSignaturePassphrase(passphrase) inputParcel = new CryptoInputParcel();
.setEnableAsciiArmorOutput(asciiArmor) }
.setCleartextSignature(cleartextSign) // override passphrase in input parcel if given by API call
.setDetachedSignature(!cleartextSign) if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
.setVersionHeader(null) inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
.setSignatureMasterKeyId(signKeyId) }
.setNfcState(nfcSignedHash, nfcCreationDate);
// execute PGP operation! // execute PGP operation!
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null); 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.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 RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
Intent result = new Intent(); PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
result.putExtra(OpenPgpApi.RESULT_INTENT,
getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo())); // return PendingIntent to be executed by client
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); Intent result = new Intent();
return result; result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
} else { result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
throw new PgpGeneralException( return result;
"Encountered unhandled type of pending action not supported by API!");
}
} else if (pgpResult.success()) { } else if (pgpResult.success()) {
Intent result = new Intent(); Intent result = new Intent();
if (pgpResult.getDetachedSignature() != null && !cleartextSign) { if (pgpResult.getDetachedSignature() != null && !cleartextSign) {
@ -372,11 +342,6 @@ public class OpenPgpService extends RemoteService {
compressionId = CompressionAlgorithmTags.UNCOMPRESSED; 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 // first try to get key ids from non-ambiguous key id extra
long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS); long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
if (keyIds == null) { if (keyIds == null) {
@ -401,9 +366,8 @@ public class OpenPgpService extends RemoteService {
long inputLength = is.available(); long inputLength = is.available();
InputData inputData = new InputData(is, inputLength, originalFilename); InputData inputData = new InputData(is, inputLength, originalFilename);
PgpSignEncryptInput pseInput = new PgpSignEncryptInput(); PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
pseInput.setSignaturePassphrase(passphrase) pseInput.setEnableAsciiArmorOutput(asciiArmor)
.setEnableAsciiArmorOutput(asciiArmor)
.setVersionHeader(null) .setVersionHeader(null)
.setCompressionId(compressionId) .setCompressionId(compressionId)
.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) .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); long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
if (signKeyId == 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); // get first usable subkey capable of signing
// carefully: only set if timestamp exists try {
Date nfcCreationDate = null; long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1); pseInput.getSignatureMasterKeyId()).getSecretSignId();
if (nfcCreationTimestamp != -1) { pseInput.setSignatureSubKeyId(signSubKeyId);
nfcCreationDate = new Date(nfcCreationTimestamp); } catch (PgpKeyNotFoundException e) {
throw new Exception("signing subkey not found!", e);
}
} }
// sign and encrypt // sign and encrypt
pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
.setSignatureMasterKeyId(signKeyId)
.setNfcState(nfcSignedHash, nfcCreationDate)
.setAdditionalEncryptId(signKeyId); // add sign key for encryption .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); PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
// execute PGP operation! // execute PGP operation!
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputData, os); PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, os);
if (pgpResult.isPending()) { if (pgpResult.isPending()) {
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) == RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) { PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) == // return PendingIntent to be executed by client
PgpSignEncryptResult.RESULT_PENDING_NFC) { Intent result = new Intent();
// return PendingIntent to execute NFC activity result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
// pass through the signature creation timestamp to be used again on second execution result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
// of PgpSignEncrypt when we have the signed hash! return result;
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!");
}
} else if (pgpResult.success()) { } else if (pgpResult.success()) {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
@ -511,11 +475,6 @@ public class OpenPgpService extends RemoteService {
os = new ParcelFileDescriptor.AutoCloseOutputStream(output); 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(); String currentPkg = getCurrentCallingPackage();
Set<Long> allowedKeyIds; Set<Long> allowedKeyIds;
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { 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 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); byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
// allow only private keys associated with accounts of this app // allow only private keys associated with accounts of this app
// no support for symmetric encryption // no support for symmetric encryption
builder.setPassphrase(passphrase) builder.setAllowSymmetricDecryption(false)
.setAllowSymmetricDecryption(false)
.setAllowedKeyIds(allowedKeyIds) .setAllowedKeyIds(allowedKeyIds)
.setDecryptMetadataOnly(decryptMetadataOnly) .setDecryptMetadataOnly(decryptMetadataOnly)
.setNfcState(nfcDecryptedSessionKey)
.setDetachedSignature(detachedSignature); .setDetachedSignature(detachedSignature);
DecryptVerifyResult pgpResult = builder.build().execute(); DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
if (pgpResult.isPending()) { if (pgpResult.isPending()) {
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) == // prepare and return PendingIntent to be executed by client
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) { RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded()); PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) { Intent result = new Intent();
throw new PgpGeneralException( result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
"Decryption of symmetric content not supported by API!"); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) == return result;
DecryptVerifyResult.RESULT_PENDING_NFC) {
// 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()) { } else if (pgpResult.success()) {
Intent result = new Intent(); Intent result = new Intent();
@ -754,7 +708,6 @@ public class OpenPgpService extends RemoteService {
* - has supported API version * - has supported API version
* - is allowed to call the service (access has been granted) * - 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 * @return null if everything is okay, or a Bundle with an error/PendingIntent
*/ */
private Intent checkRequirements(Intent data) { private Intent checkRequirements(Intent data) {
@ -794,9 +747,7 @@ public class OpenPgpService extends RemoteService {
return null; return null;
} }
// TODO: multi-threading
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() { private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
@Override @Override
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) { public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
try { try {
@ -806,30 +757,42 @@ public class OpenPgpService extends RemoteService {
} }
String action = data.getAction(); String action = data.getAction();
if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) { switch (action) {
return signImpl(data, input, output, true); case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
} else if (OpenPgpApi.ACTION_SIGN.equals(action)) { return signImpl(data, input, output, true);
// 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!"); case OpenPgpApi.ACTION_SIGN: {
return signImpl(data, input, output, true); // DEPRECATED: same as ACTION_CLEARTEXT_SIGN
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) { 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, false); return signImpl(data, input, output, true);
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { }
return encryptAndSignImpl(data, input, output, false); case OpenPgpApi.ACTION_DETACHED_SIGN: {
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) { return signImpl(data, input, output, false);
return encryptAndSignImpl(data, input, output, true); }
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { case OpenPgpApi.ACTION_ENCRYPT: {
return decryptAndVerifyImpl(data, input, output, false); return encryptAndSignImpl(data, input, output, false);
} else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) { }
return decryptAndVerifyImpl(data, input, output, true); case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
} else if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(action)) { return encryptAndSignImpl(data, input, output, true);
return getSignKeyIdImpl(data); }
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
return getKeyIdsImpl(data); return decryptAndVerifyImpl(data, input, output, false);
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) { }
return getKeyImpl(data); case OpenPgpApi.ACTION_DECRYPT_METADATA: {
} else { return decryptAndVerifyImpl(data, input, output, true);
return null; }
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 { } finally {
// always close input and output file descriptors even in error cases // 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.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings; 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; import org.sufficientlysecure.keychain.util.Log;
public class AccountSettingsActivity extends BaseActivity { 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.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.BaseActivity;
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
import org.sufficientlysecure.keychain.util.Log; 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.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings; 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.SelectPublicKeyFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; 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.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing; 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.ui.CreateKeyActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;

View File

@ -22,10 +22,14 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.io.Serializable; import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Date;
import java.util.Map;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; 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.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult; 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.operations.results.ExportResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.operations.results.CertifyResult; 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.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
@ -159,8 +159,6 @@ public class KeychainIntentService extends IntentService implements Progressable
// decrypt/verify // decrypt/verify
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes"; 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 // keybase proof
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint"; public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
@ -169,6 +167,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// save keyring // save keyring
public static final String EDIT_KEYRING_PARCEL = "save_parcel"; public static final String EDIT_KEYRING_PARCEL = "save_parcel";
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase"; public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
// delete keyring(s) // delete keyring(s)
public static final String DELETE_KEY_LIST = "delete_list"; public static final String DELETE_KEY_LIST = "delete_list";
@ -193,7 +192,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// promote key // promote key
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; 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 // consolidate
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
@ -260,11 +259,12 @@ public class KeychainIntentService extends IntentService implements Progressable
// Input // Input
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
String keyServerUri = data.getString(UPLOAD_KEY_SERVER); String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
// Operation // Operation
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled); CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
CertifyResult result = op.certify(parcel, keyServerUri); CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
// Result // Result
sendMessageToHandler(MessageStatus.OKAY, result); sendMessageToHandler(MessageStatus.OKAY, result);
@ -289,27 +289,20 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_DECRYPT_METADATA: { case ACTION_DECRYPT_METADATA: {
try { try {
/* Input */ /* Input */
Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE); CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
InputData inputData = createDecryptInputData(data); InputData inputData = createDecryptInputData(data);
/* Operation */
Bundle resultData = new Bundle();
// verifyText and decrypt returning additional resultData values for the // verifyText and decrypt returning additional resultData values for the
// verification of signatures // verification of signatures
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
this, new ProviderHelper(this), this, inputData, null this, new ProviderHelper(this), this, inputData, null
); );
builder.setAllowSymmetricDecryption(true) builder.setAllowSymmetricDecryption(true)
.setPassphrase(passphrase) .setDecryptMetadataOnly(true);
.setDecryptMetadataOnly(true)
.setNfcState(nfcDecryptedSessionKey);
DecryptVerifyResult decryptVerifyResult = builder.build().execute(); DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult); sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
} catch (Exception e) { } catch (Exception e) {
@ -384,7 +377,8 @@ public class KeychainIntentService extends IntentService implements Progressable
); );
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint); builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
DecryptVerifyResult decryptVerifyResult = builder.build().execute(); DecryptVerifyResult decryptVerifyResult = builder.build().execute(
new CryptoInputParcel());
outStream.close(); outStream.close();
if (!decryptVerifyResult.success()) { if (!decryptVerifyResult.success()) {
@ -419,15 +413,13 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_DECRYPT_VERIFY: { case ACTION_DECRYPT_VERIFY: {
try { try {
/* Input */ /* Input */
Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE); CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
InputData inputData = createDecryptInputData(data); InputData inputData = createDecryptInputData(data);
OutputStream outStream = createCryptOutputStream(data); OutputStream outStream = createCryptOutputStream(data);
/* Operation */ /* Operation */
Bundle resultData = new Bundle(); Bundle resultData = new Bundle();
// verifyText and decrypt returning additional resultData values for the // 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, this, new ProviderHelper(this), this,
inputData, outStream inputData, outStream
); );
builder.setAllowSymmetricDecryption(true) builder.setAllowSymmetricDecryption(true);
.setPassphrase(passphrase)
.setNfcState(nfcDecryptedSessionKey);
DecryptVerifyResult decryptVerifyResult = builder.build().execute(); DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
outStream.close(); outStream.close();
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult); resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
/* Output */ /* Output */
finalizeDecryptOutputStream(data, resultData, outStream); finalizeDecryptOutputStream(data, resultData, outStream);
Log.logDebugBundle(resultData, "resultData"); Log.logDebugBundle(resultData, "resultData");
sendMessageToHandler(MessageStatus.OKAY, resultData); sendMessageToHandler(MessageStatus.OKAY, resultData);
} catch (Exception e) {
} catch (IOException | PgpGeneralException e) {
// TODO get rid of this!
sendErrorToHandler(e); sendErrorToHandler(e);
} }
@ -478,11 +468,11 @@ public class KeychainIntentService extends IntentService implements Progressable
// Input // Input
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
Passphrase passphrase = data.getParcelable(EDIT_KEYRING_PASSPHRASE); CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation // Operation
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled); EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
EditKeyResult result = op.execute(saveParcel, passphrase); OperationResult result = op.execute(saveParcel, cryptoInput);
// Result // Result
sendMessageToHandler(MessageStatus.OKAY, result); sendMessageToHandler(MessageStatus.OKAY, result);
@ -492,11 +482,12 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_PROMOTE_KEYRING: { case ACTION_PROMOTE_KEYRING: {
// Input // 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 // Operation
PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);
PromoteKeyResult result = op.execute(keyRingId); PromoteKeyResult result = op.execute(keyRingId, cardAid);
// Result // Result
sendMessageToHandler(MessageStatus.OKAY, result); sendMessageToHandler(MessageStatus.OKAY, result);
@ -553,11 +544,12 @@ public class KeychainIntentService extends IntentService implements Progressable
// Input // Input
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL); SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation // Operation
SignEncryptOperation op = new SignEncryptOperation( SignEncryptOperation op = new SignEncryptOperation(
this, new ProviderHelper(this), this, mActionCanceled); this, new ProviderHelper(this), this, mActionCanceled);
SignEncryptResult result = op.execute(inputParcel); SignEncryptResult result = op.execute(inputParcel, cryptoInput);
// Result // Result
sendMessageToHandler(MessageStatus.OKAY, result); sendMessageToHandler(MessageStatus.OKAY, result);

View File

@ -60,18 +60,18 @@ import java.util.Date;
* *
* Caching behavior for subkeys depends on the cacheSubs preference: * 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 * - 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 * 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 * 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. * 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 * - 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 * 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 * 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 * 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 * 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 * rare occurrence, and caching by keyring is what the user expects in the vast majority of
* cases, this is not the default behavior. * cases, this is not the default behavior.
* *
*/ */
public class PassphraseCacheService extends Service { public class PassphraseCacheService extends Service {
@ -123,7 +123,7 @@ public class PassphraseCacheService extends Service {
public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId, public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId,
Passphrase passphrase, Passphrase passphrase,
String primaryUserId) { 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 intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD); intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
@ -137,10 +137,23 @@ public class PassphraseCacheService extends Service {
context.startService(intent); 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 * 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. * designed to wait until the service returns the passphrase.
*
* @return passphrase or null (if no passphrase is cached for this keyId) * @return passphrase or null (if no passphrase is cached for this keyId)
*/ */
public static Passphrase getCachedPassphrase(Context context, long masterKeyId, long subKeyId) throws KeyNotFoundException { 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 // on "none" key, just do nothing
if(masterKeyId == Constants.key.none) { if (masterKeyId == Constants.key.none) {
return null; return null;
} }
@ -232,11 +245,11 @@ public class PassphraseCacheService extends Service {
switch (keyType) { switch (keyType) {
case DIVERT_TO_CARD: case DIVERT_TO_CARD:
if (Preferences.getPreferences(this).useDefaultYubikeyPin()) { if (Preferences.getPreferences(this).useDefaultYubiKeyPin()) {
Log.d(Constants.TAG, "PassphraseCacheService: Using default Yubikey PIN: 123456"); 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/ return new Passphrase("123456"); // default YubiKey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
} else { } else {
Log.d(Constants.TAG, "PassphraseCacheService: NOT using default Yubikey PIN"); Log.d(Constants.TAG, "PassphraseCacheService: NOT using default YubiKey PIN");
break; break;
} }
case PASSPHRASE_EMPTY: 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 * 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 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 // 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); PendingIntent.FLAG_CANCEL_CURRENT);
} }
@ -325,11 +338,17 @@ public class PassphraseCacheService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()"); Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()");
if (intent == null || intent.getAction() == null) {
updateService();
return START_STICKY;
}
// register broadcastreceiver // register broadcastreceiver
registerReceiver(); registerReceiver();
if (intent != null && intent.getAction() != null) { String action = intent.getAction();
if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) { switch (action) {
case ACTION_PASSPHRASE_CACHE_ADD: {
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL); long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1); long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_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, // 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 // just add master key id to the cache, otherwise, add this specific subkey to the cache
if (subKeyId == masterKeyId || !Preferences.getPreferences(mContext).getPassphraseCacheSubs()) { long referenceKeyId =
mPassphraseCache.put(masterKeyId, new CachedPassphrase(passphrase, primaryUserID)); Preferences.getPreferences(mContext).getPassphraseCacheSubs() ? subKeyId : masterKeyId;
if (ttl > 0) { mPassphraseCache.put(referenceKeyId, new CachedPassphrase(passphrase, primaryUserID));
// register new alarm with keyId for this passphrase if (ttl > 0) {
long triggerTime = new Date().getTime() + (ttl * 1000); // register new alarm with keyId for this passphrase
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); long triggerTime = new Date().getTime() + (ttl * 1000);
am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, masterKeyId)); AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
} am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, referenceKeyId));
} 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));
}
} }
break;
updateService(); }
} else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) { case ACTION_PASSPHRASE_CACHE_GET: {
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric); long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric);
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric); long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric);
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
@ -392,22 +402,42 @@ public class PassphraseCacheService extends Service {
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", 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); AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
// Stop all ttl alarms if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) {
for (int i = 0; i < mPassphraseCache.size(); i++) {
am.cancel(buildIntent(this, mPassphraseCache.keyAt(i))); 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();
} }
break;
mPassphraseCache.clear(); }
default: {
updateService();
} else {
Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!"); Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
break;
} }
} }
updateService();
return START_STICKY; return START_STICKY;
} }

View File

@ -82,10 +82,14 @@ public class SaveKeyringParcel implements Parcelable {
mRevokeSubKeys = new ArrayList<>(); mRevokeSubKeys = new ArrayList<>();
} }
public boolean isEmpty() {
return isRestrictedOnly() && mChangeSubKeys.isEmpty();
}
/** Returns true iff this parcel does not contain any operations which require a passphrase. */ /** Returns true iff this parcel does not contain any operations which require a passphrase. */
public boolean isRestrictedOnly() { public boolean isRestrictedOnly() {
if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty() if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty()
|| !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty() || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeUserIds.isEmpty()
|| !mRevokeSubKeys.isEmpty()) { || !mRevokeSubKeys.isEmpty()) {
return false; return false;
} }

View File

@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.service; package org.sufficientlysecure.keychain.service;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
@ -26,6 +27,7 @@ import android.support.v4.app.FragmentManager;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; 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.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
public class CertifyFingerprintActivity extends BaseActivity { public class CertifyFingerprintActivity extends BaseActivity {

View File

@ -19,6 +19,8 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
/** /**
* Signs the specified public key with the specified secret master key * 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.database.MatrixCursor;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.net.Uri; import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle; import android.os.Bundle;
import android.os.Message; import android.os.Message;
import android.os.Messenger; 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.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; 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.adapter.MultiUserIdsAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.lang.reflect.Method;
import java.util.ArrayList; 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; private CheckBox mUploadKeyCheckbox;
ListView mUserIds; ListView mUserIds;
@ -102,9 +98,6 @@ public class CertifyKeyFragment extends LoaderFragment
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
// Start out with a progress indicator.
setContentShown(false);
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS); mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
if (mPubMasterKeyIds == null) { if (mPubMasterKeyIds == null) {
Log.e(Constants.TAG, "List of key ids to certify missing!"); Log.e(Constants.TAG, "List of key ids to certify missing!");
@ -114,6 +107,7 @@ public class CertifyKeyFragment extends LoaderFragment
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra( mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
KeychainIntentService.EXTRA_MESSENGER); KeychainIntentService.EXTRA_MESSENGER);
mPassthroughMessenger = null; // TODO remove, development hack
// preselect certify key id if given // preselect certify key id if given
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
@ -143,9 +137,7 @@ public class CertifyKeyFragment extends LoaderFragment
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { 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, null);
View view = inflater.inflate(R.layout.certify_key_fragment, getContainer());
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); 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.create(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR).show(); Notify.Style.ERROR).show();
} else { } else {
initiateCertifying(); cryptoOperation(new CryptoInputParcel());
} }
} }
}); });
@ -183,7 +175,7 @@ public class CertifyKeyFragment extends LoaderFragment
mUploadKeyCheckbox.setChecked(false); mUploadKeyCheckbox.setChecked(false);
} }
return root; return view;
} }
@Override @Override
@ -222,17 +214,6 @@ public class CertifyKeyFragment extends LoaderFragment
}) { }) {
@Override @Override
public byte[] getBlob(int column) { 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); return super.getBlob(column);
} }
}; };
@ -307,7 +288,6 @@ public class CertifyKeyFragment extends LoaderFragment
} }
mUserIdsAdapter.swapCursor(matrix); mUserIdsAdapter.swapCursor(matrix);
setContentShown(true, isResumed());
} }
@Override @Override
@ -315,49 +295,8 @@ public class CertifyKeyFragment extends LoaderFragment
mUserIdsAdapter.swapCursor(null); 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 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { protected void cryptoOperation(CryptoInputParcel cryptoInput) {
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() {
// Bail out if there is not at least one user id selected // Bail out if there is not at least one user id selected
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions(); ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
if (certifyActions.isEmpty()) { if (certifyActions.isEmpty()) {
@ -372,6 +311,7 @@ public class CertifyKeyFragment extends LoaderFragment
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId); CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
parcel.mCertifyActions.addAll(certifyActions); parcel.mCertifyActions.addAll(certifyActions);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel); data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
if (mUploadKeyCheckbox.isChecked()) { if (mUploadKeyCheckbox.isChecked()) {
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
@ -396,11 +336,17 @@ public class CertifyKeyFragment extends LoaderFragment
true, true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first // handle messages by KeychainIntentCryptoServiceHandler first
super.handleMessage(message); super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) { if (message.arg1 == MessageStatus.OKAY.ordinal()) {
Bundle data = message.getData(); Bundle data = message.getData();
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
Intent intent = new Intent(); Intent intent = new Intent();

View File

@ -17,17 +17,25 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.view.View;
import org.sufficientlysecure.keychain.R; 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 org.sufficientlysecure.keychain.util.Passphrase;
import java.io.IOException;
import java.util.ArrayList; 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_NAME = "name";
public static final String EXTRA_EMAIL = "email"; 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_ADDITIONAL_EMAILS = "additional_emails";
public static final String EXTRA_PASSPHRASE = "passphrase"; 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"; public static final String FRAGMENT_TAG = "currentFragment";
String mName; String mName;
@ -60,14 +72,29 @@ public class CreateKeyActivity extends BaseActivity {
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
} else { } 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 Intent intent = getIntent();
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance(); // Initialize members with default values for a new instance
loadFragment(frag, FragAction.START); 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) { 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 @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@ -125,8 +184,14 @@ public class CreateKeyActivity extends BaseActivity {
break; break;
} }
// do it immediately! // do it immediately!
getSupportFragmentManager().executePendingTransactions(); getSupportFragmentManager().executePendingTransactions();
}
interface NfcListenerFragment {
public void onNfcPerform() throws IOException;
} }
} }

View File

@ -126,7 +126,7 @@ public class CreateKeyEmailFragment extends Fragment {
if (mAdditionalEmailModels == null) { if (mAdditionalEmailModels == null) {
mAdditionalEmailModels = new ArrayList<>(); mAdditionalEmailModels = new ArrayList<>();
if (mCreateKeyActivity.mAdditionalEmails != null) { if (mCreateKeyActivity.mAdditionalEmails != null) {
setAdditionalEmails(mCreateKeyActivity.mAdditionalEmails); mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
} }
} }
@ -209,12 +209,6 @@ public class CreateKeyEmailFragment extends Fragment {
return emails; return emails;
} }
private void setAdditionalEmails(ArrayList<String> emails) {
for (String email : emails) {
mAdditionalEmailModels.add(new EmailAdapter.ViewModel(email));
}
}
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@ -244,8 +238,7 @@ public class CreateKeyEmailFragment extends Fragment {
// Provide a reference to the views for each data item // Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and // 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 // you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView mTextView; public TextView mTextView;
public ImageButton mDeleteButton; public ImageButton mDeleteButton;
@ -289,7 +282,10 @@ public class CreateKeyEmailFragment extends Fragment {
// Replace the contents of a view (invoked by the layout manager) // Replace the contents of a view (invoked by the layout manager)
@Override @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { 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; ViewHolder thisHolder = (ViewHolder) holder;
// - get element from your dataset at this position // - get element from your dataset at this position
// - replace the contents of the view with that element // - replace the contents of the view with that element
@ -302,9 +298,6 @@ public class CreateKeyEmailFragment extends Fragment {
remove(model); 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); 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) { public void remove(ViewModel model) {
int position = mDataset.indexOf(model); int position = mDataset.indexOf(model);
mDataset.remove(position); mDataset.remove(position);

View File

@ -78,7 +78,7 @@ public class CreateKeyStartFragment extends Fragment {
mCreateKey = view.findViewById(R.id.create_key_create_key_button); mCreateKey = view.findViewById(R.id.create_key_create_key_button);
mImportKey = view.findViewById(R.id.create_key_import_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); mCancel = (TextView) view.findViewById(R.id.create_key_cancel);
if (mCreateKeyActivity.mFirstTime) { 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() { mImportKey.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { 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.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.View; import android.view.View;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
public class DecryptFilesActivity extends BaseActivity { public class DecryptFilesActivity extends BaseActivity {

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
@ -32,13 +33,13 @@ import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.TextView; import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType; import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; 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.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
@ -63,6 +64,8 @@ public class DecryptFilesFragment extends DecryptFragment {
private Uri mInputUri = null; private Uri mInputUri = null;
private Uri mOutputUri = null; private Uri mOutputUri = null;
private String mCurrentCryptoOperation;
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
*/ */
@ -90,9 +93,6 @@ public class DecryptFilesFragment extends DecryptFragment {
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() { view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
// reset state
mPassphrase = null;
mNfcDecryptedSessionKey = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT); FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else { } else {
@ -144,7 +144,7 @@ public class DecryptFilesFragment extends DecryptFragment {
return; return;
} }
decryptOriginalFilename(); startDecryptFilenames();
} }
private String removeEncryptedAppend(String name) { private String removeEncryptedAppend(String name) {
@ -157,110 +157,45 @@ public class DecryptFilesFragment extends DecryptFragment {
} }
private void askForOutputFilename(String originalFilename) { private void askForOutputFilename(String originalFilename) {
String targetName; if (TextUtils.isEmpty(originalFilename)) {
if (!TextUtils.isEmpty(originalFilename)) { originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
targetName = originalFilename;
} else {
targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
File file = new File(mInputUri.getPath()); File file = new File(mInputUri.getPath());
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; 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), FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
} else { } else {
FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT); FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT);
} }
} }
private void decryptOriginalFilename() { private void startDecrypt() {
Log.d(Constants.TAG, "decryptOriginalFilename"); mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
cryptoOperation(new CryptoInputParcel());
}
Intent intent = new Intent(getActivity(), KeychainIntentService.class); private void startDecryptFilenames() {
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
// fill values for this action cryptoOperation(new CryptoInputParcel());
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);
} }
@Override @Override
protected void decryptStart() { @SuppressLint("HandlerLeak")
Log.d(Constants.TAG, "decryptStart"); protected void cryptoOperation(CryptoInputParcel cryptoInput) {
// Send all information needed to service to decrypt in other thread // Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class); Intent intent = new Intent(getActivity(), KeychainIntentService.class);
// fill values for this action // fill values for this action
Bundle data = new Bundle(); Bundle data = new Bundle();
// use current operation, either decrypt metadata or decrypt payload
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); intent.setAction(mCurrentCryptoOperation);
// data // data
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri); Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal()); data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
@ -269,8 +204,7 @@ public class DecryptFilesFragment extends DecryptFragment {
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal()); data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri); data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@ -280,10 +214,16 @@ public class DecryptFilesFragment extends DecryptFragment {
getString(R.string.progress_decrypting), getString(R.string.progress_decrypting),
ProgressDialog.STYLE_HORIZONTAL, ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) { ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
@Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) { if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle // get returned data bundle
Bundle returnData = message.getData(); Bundle returnData = message.getData();
@ -291,39 +231,39 @@ public class DecryptFilesFragment extends DecryptFragment {
DecryptVerifyResult pgpResult = DecryptVerifyResult pgpResult =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
if (pgpResult.isPending()) { if (pgpResult.success()) {
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()) {
// display signature result in activity switch (mCurrentCryptoOperation) {
onResult(pgpResult); 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()) { if (mDeleteAfter.isChecked()) {
// Create and show dialog to delete original file // Create and show dialog to delete original file
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
setInputUri(null); setInputUri(null);
} }
/* /*
// A future open after decryption feature // A future open after decryption feature
if () { if () {
Intent viewFile = new Intent(Intent.ACTION_VIEW); Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setInputData(mOutputUri); viewFile.setInputData(mOutputUri);
startActivity(viewFile); startActivity(viewFile);
}
*/
break;
}
default: {
Log.e(Constants.TAG, "Bug: not supported operation!");
break;
}
} }
*/
} else { } else {
pgpResult.createNotify(getActivity()).show(); pgpResult.createNotify(getActivity()).show();
} }
@ -346,22 +286,6 @@ public class DecryptFilesFragment extends DecryptFragment {
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) { 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: { case REQUEST_CODE_INPUT: {
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
setInputUri(data.getData()); setInputUri(data.getData());
@ -373,7 +297,7 @@ public class DecryptFilesFragment extends DecryptFragment {
// This happens after output file was selected, so start our operation // This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
mOutputUri = data.getData(); mOutputUri = data.getData();
decryptStart(); startDecrypt();
} }
return; 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.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -30,16 +29,13 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract; 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;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; 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; 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 long mSignatureKeyId = 0;
protected LinearLayout mResultLayout; protected LinearLayout mResultLayout;
@ -56,11 +52,6 @@ public abstract class DecryptFragment extends Fragment {
protected TextView mSignatureEmail; protected TextView mSignatureEmail;
protected TextView mSignatureAction; protected TextView mSignatureAction;
// State
protected Passphrase mPassphrase;
protected byte[] mNfcDecryptedSessionKey;
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
@ -95,25 +86,6 @@ public abstract class DecryptFragment extends Fragment {
startActivity(viewKeyIntent); 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. * @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.OperationResult;
import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;

View File

@ -17,7 +17,6 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -30,7 +29,6 @@ import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; 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;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType; import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; 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.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -51,10 +50,7 @@ public class DecryptTextFragment extends DecryptFragment {
// view // view
private LinearLayout mValidLayout; private LinearLayout mValidLayout;
private LinearLayout mInvalidLayout; private LinearLayout mInvalidLayout;
private Button mInvalidButton;
private TextView mText; private TextView mText;
private View mShareButton;
private View mCopyButton;
// model // model
private String mCiphertext; private String mCiphertext;
@ -81,23 +77,26 @@ public class DecryptTextFragment extends DecryptFragment {
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid); mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid);
mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid); 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); 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); View vShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
mShareButton.setOnClickListener(new View.OnClickListener() { vShareButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
startActivity(sendWithChooserExcludingEncrypt(mText.getText().toString())); 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 @Override
public void onClick(View v) { public void onClick(View v) {
copyToClipboard(mText.getText().toString()); 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 @Override
public void onClick(View v) { public void onClick(View v) {
mInvalidLayout.setVisibility(View.GONE); mInvalidLayout.setVisibility(View.GONE);
@ -143,14 +142,12 @@ public class DecryptTextFragment extends DecryptFragment {
String ciphertext = getArguments().getString(ARG_CIPHERTEXT); String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
if (ciphertext != null) { if (ciphertext != null) {
mCiphertext = ciphertext; mCiphertext = ciphertext;
decryptStart(); cryptoOperation(new CryptoInputParcel());
} }
} }
@Override @Override
protected void decryptStart() { protected void cryptoOperation(CryptoInputParcel cryptoInput) {
Log.d(Constants.TAG, "decryptStart");
// Send all information needed to service to decrypt in other thread // Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class); Intent intent = new Intent(getActivity(), KeychainIntentService.class);
@ -160,10 +157,10 @@ public class DecryptTextFragment extends DecryptFragment {
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
// data // data
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal()); data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes()); data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase); data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@ -177,6 +174,11 @@ public class DecryptTextFragment extends DecryptFragment {
// handle messages by standard KeychainIntentServiceHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) { if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle // get returned data bundle
Bundle returnData = message.getData(); Bundle returnData = message.getData();
@ -184,20 +186,7 @@ public class DecryptTextFragment extends DecryptFragment {
DecryptVerifyResult pgpResult = DecryptVerifyResult pgpResult =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT); returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
if (pgpResult.isPending()) { if (pgpResult.success()) {
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()) {
byte[] decryptedMessage = returnData byte[] decryptedMessage = returnData
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES); .getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
@ -245,34 +234,4 @@ public class DecryptTextFragment extends DecryptFragment {
getActivity().startService(intent); 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.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
public class EditKeyActivity extends BaseActivity { 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.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; 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.SubkeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; 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.dialog.*;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
public class EditKeyFragment extends LoaderFragment implements
public class EditKeyFragment extends CryptoOperationFragment implements
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri"; public static final String ARG_DATA_URI = "uri";
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
private ListView mUserIdsList; private ListView mUserIdsList;
private ListView mSubkeysList; private ListView mSubkeysList;
private ListView mUserIdsAddedList; private ListView mUserIdsAddedList;
@ -96,7 +96,6 @@ public class EditKeyFragment extends LoaderFragment implements
private SaveKeyringParcel mSaveKeyringParcel; private SaveKeyringParcel mSaveKeyringParcel;
private String mPrimaryUserId; private String mPrimaryUserId;
private Passphrase mCurrentPassphrase;
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
@ -125,8 +124,7 @@ public class EditKeyFragment extends LoaderFragment implements
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { 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, null);
View view = inflater.inflate(R.layout.edit_key_fragment, getContainer());
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids); mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys); 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); mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id);
mAddSubkey = view.findViewById(R.id.edit_key_action_add_key); mAddSubkey = view.findViewById(R.id.edit_key_action_add_key);
return root; return view;
} }
@Override @Override
@ -151,7 +149,7 @@ public class EditKeyFragment extends LoaderFragment implements
if (mDataUri == null) { if (mDataUri == null) {
returnKeyringParcel(); returnKeyringParcel();
} else { } else {
saveInDatabase(mCurrentPassphrase); cryptoOperation(new CryptoInputParcel());
} }
} }
}, new OnClickListener() { }, new OnClickListener() {
@ -181,18 +179,12 @@ public class EditKeyFragment extends LoaderFragment implements
private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) { private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) {
mSaveKeyringParcel = saveKeyringParcel; mSaveKeyringParcel = saveKeyringParcel;
mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId; mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId;
if (saveKeyringParcel.mNewUnlock != null) {
mCurrentPassphrase = saveKeyringParcel.mNewUnlock.mNewPassphrase;
}
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true); mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true);
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true); mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true);
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
// show directly
setContentShown(true);
} }
private void loadData(Uri dataUri) { private void loadData(Uri dataUri) {
@ -212,9 +204,6 @@ public class EditKeyFragment extends LoaderFragment implements
case GNU_DUMMY: case GNU_DUMMY:
finishWithError(LogType.MSG_EK_ERROR_DUMMY); finishWithError(LogType.MSG_EK_ERROR_DUMMY);
return; return;
case DIVERT_TO_CARD:
finishWithError(LogType.MSG_EK_ERROR_DIVERT);
break;
} }
mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint()); mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
@ -225,24 +214,10 @@ public class EditKeyFragment extends LoaderFragment implements
return; return;
} }
try { // Prepare the loaders. Either re-connect with an existing ones,
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), // or start new ones.
mSaveKeyringParcel.mMasterKeyId, mSaveKeyringParcel.mMasterKeyId); getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
} catch (PassphraseCacheService.KeyNotFoundException e) { getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
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);
}
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel); mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel);
mUserIdsList.setAdapter(mUserIdsAdapter); mUserIdsList.setAdapter(mUserIdsAdapter);
@ -258,28 +233,6 @@ public class EditKeyFragment extends LoaderFragment implements
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); 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() { private void initView() {
mChangePassphrase.setOnClickListener(new View.OnClickListener() { mChangePassphrase.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -318,7 +271,6 @@ public class EditKeyFragment extends LoaderFragment implements
} }
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) { switch (id) {
case LOADER_ID_USER_IDS: { case LOADER_ID_USER_IDS: {
@ -351,7 +303,6 @@ public class EditKeyFragment extends LoaderFragment implements
break; break;
} }
setContentShown(true);
} }
/** /**
@ -393,7 +344,7 @@ public class EditKeyFragment extends LoaderFragment implements
Messenger messenger = new Messenger(returnHandler); Messenger messenger = new Messenger(returnHandler);
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance( SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
messenger, mCurrentPassphrase, R.string.title_change_passphrase); messenger, R.string.title_change_passphrase);
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog"); setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
} }
@ -589,8 +540,11 @@ public class EditKeyFragment extends LoaderFragment implements
getActivity().finish(); getActivity().finish();
} }
private void saveInDatabase(Passphrase passphrase) { @Override
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString()); protected void cryptoOperation(CryptoInputParcel cryptoInput) {
Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
ServiceProgressHandler saveHandler = new ServiceProgressHandler( ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(), getActivity(),
@ -602,6 +556,10 @@ public class EditKeyFragment extends LoaderFragment implements
// handle messages by standard KeychainIntentServiceHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) { if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle // get returned data bundle
@ -637,7 +595,7 @@ public class EditKeyFragment extends LoaderFragment implements
// fill values for this action // fill values for this action
Bundle data = new Bundle(); 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); data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); 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 { public class EncryptDecryptOverviewFragment extends Fragment {
View mEncryptFile;
View mEncryptText;
View mDecryptFile;
View mDecryptFromClipboard;
View mClipboardIcon; View mClipboardIcon;
@Override @Override
@ -53,10 +49,10 @@ public class EncryptDecryptOverviewFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false); View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false);
mEncryptFile = view.findViewById(R.id.encrypt_files); View mEncryptFile = view.findViewById(R.id.encrypt_files);
mEncryptText = view.findViewById(R.id.encrypt_text); View mEncryptText = view.findViewById(R.id.encrypt_text);
mDecryptFile = view.findViewById(R.id.decrypt_files); View mDecryptFile = view.findViewById(R.id.decrypt_files);
mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard); View mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
mClipboardIcon = view.findViewById(R.id.clipboard_icon); mClipboardIcon = view.findViewById(R.id.clipboard_icon);
mEncryptFile.setOnClickListener(new View.OnClickListener() { 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> * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -18,32 +18,25 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.view.Menu; import android.support.v4.app.FragmentTransaction;
import android.view.MenuItem; import android.view.View;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity;
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.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ShareHelper;
import java.util.ArrayList; 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 */ /* Intents */
public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA; 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_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS"; public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS";
// view Fragment mModeFragment;
private int mCurrentMode = MODE_ASYMMETRIC; EncryptFilesFragment mEncryptFragment;
// 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;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setFullScreenDialogClose(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}, false);
// Handle intent actions // Handle intent actions
handleActions(getIntent()); handleActions(getIntent(), savedInstanceState);
updateModeFragment();
} }
@Override @Override
@ -358,73 +71,10 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
setContentView(R.layout.encrypt_files_activity); 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 * Handles all actions with this intent
*
* @param intent
*/ */
private void handleActions(Intent intent) { private void handleActions(Intent intent, Bundle savedInstanceState) {
String action = intent.getAction(); String action = intent.getAction();
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
String type = intent.getType(); String type = intent.getType();
@ -453,14 +103,56 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 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 if (savedInstanceState == null) {
mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
// Save uris mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
mInputUris = uris; 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 * 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 * it under the terms of the GNU General Public License as published by
@ -17,57 +17,130 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Point; import android.graphics.Point;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; 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.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.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.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.FileHelper; 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.io.File;
import java.util.HashMap; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet; 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"; public static final String ARG_URIS = "uris";
private static final int REQUEST_CODE_INPUT = 0x00007003; private static final int REQUEST_CODE_INPUT = 0x00007003;
private static final int REQUEST_CODE_OUTPUT = 0x00007007; private static final int REQUEST_CODE_OUTPUT = 0x00007007;
private EncryptActivityInterface mEncryptInterface; private IMode mModeInterface;
// view private boolean mSymmetricMode = false;
private View mAddView; private boolean mUseArmor = false;
private ListView mSelectedFiles; private boolean mUseCompression = true;
private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter(); private boolean mDeleteAfterEncrypt = false;
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>(); 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 @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
try { try {
mEncryptInterface = (EncryptActivityInterface) activity; mModeInterface = (IMode) activity;
} catch (ClassCastException e) { } 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 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_files_fragment, container, false); 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); mSelectedFiles.addItemDecoration(new SpacesItemDecoration(
mAddView.setOnClickListener(new View.OnClickListener() { 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 @Override
public void onClick(View v) { public void onClick(View v) {
addInputUri(); addInputUri();
} }
}); });
mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list);
mSelectedFiles.addFooterView(mAddView); ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_URIS);
mSelectedFiles.setAdapter(mAdapter); if (inputUris != null) {
mFilesAdapter.addAll(inputUris);
}
mUseArmor = getArguments().getBoolean(ARG_USE_ASCII_ARMOR);
mSelectedFiles.setAdapter(mFilesAdapter);
return view; return view;
} }
@ -95,7 +179,6 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@ -103,8 +186,8 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT); FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
} else { } else {
FileHelper.openFile(EncryptFilesFragment.this, mEncryptInterface.getInputUris().isEmpty() ? FileHelper.openFile(EncryptFilesFragment.this, mFilesModels.isEmpty() ?
null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1), null : mFilesModels.get(mFilesModels.size() - 1).inputUri,
"*/*", REQUEST_CODE_INPUT); "*/*", REQUEST_CODE_INPUT);
} }
} }
@ -114,34 +197,27 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
return; return;
} }
if (mEncryptInterface.getInputUris().contains(inputUri)) { try {
mFilesAdapter.add(inputUri);
} catch (IOException e) {
Notify.create(getActivity(), Notify.create(getActivity(),
getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)),
Notify.Style.ERROR).show(this); Notify.Style.ERROR).show();
return; return;
} }
mEncryptInterface.getInputUris().add(inputUri);
mEncryptInterface.notifyUpdate();
mSelectedFiles.requestFocus();
}
private void delInputUri(int position) {
mEncryptInterface.getInputUris().remove(position);
mEncryptInterface.notifyUpdate();
mSelectedFiles.requestFocus(); mSelectedFiles.requestFocus();
} }
private void showOutputFileDialog() { private void showOutputFileDialog() {
if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) { if (mFilesModels.size() > 1 || mFilesModels.isEmpty()) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
Uri inputUri = mEncryptInterface.getInputUris().get(0); FilesAdapter.ViewModel model = mFilesModels.get(0);
String targetName = String targetName =
(mEncryptInterface.isEncryptFilenames() ? "1" : FileHelper.getFilename(getActivity(), inputUri)) (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 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 parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
File targetFile = new File(parentDir, targetName); File targetFile = new File(parentDir, targetName);
FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file), 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) { private void encryptClicked(boolean share) {
if (mEncryptInterface.getInputUris().isEmpty()) { if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this); Notify.create(getActivity(), R.string.error_no_file_selected,
Notify.Style.ERROR).show();
return; return;
} }
if (share) { if (share) {
mEncryptInterface.getOutputUris().clear(); mOutputUris.clear();
int filenameCounter = 1; int filenameCounter = 1;
for (Uri uri : mEncryptInterface.getInputUris()) { for (FilesAdapter.ViewModel model : mFilesModels) {
String targetName = String targetName =
(mEncryptInterface.isEncryptFilenames() ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), uri)) (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName)); mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
filenameCounter++; filenameCounter++;
} }
mEncryptInterface.startEncrypt(true); startEncrypt(true);
} else { } else {
if (mEncryptInterface.getInputUris().size() > 1) { if (mFilesModels.size() > 1) {
Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show(this); Notify.create(getActivity(), R.string.error_multi_not_supported,
Notify.Style.ERROR).show();
return; return;
} }
showOutputFileDialog(); showOutputFileDialog();
} }
} }
@TargetApi(Build.VERSION_CODES.KITKAT) public void addFile(Intent data) {
public boolean handleClipData(Intent data) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
if (data.getClipData() != null && data.getClipData().getItemCount() > 0) { addInputUri(data.getData());
for (int i = 0; i < data.getClipData().getItemCount(); i++) { } else {
Uri uri = data.getClipData().getItemAt(i).getUri(); if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
if (uri != null) addInputUri(uri); 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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.isCheckable()) {
item.setChecked(!item.isChecked());
}
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.encrypt_save: { case R.id.encrypt_save: {
encryptClicked(false); encryptClicked(false);
@ -199,6 +292,32 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
encryptClicked(true); encryptClicked(true);
break; 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: { default: {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -206,24 +325,234 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
return true; 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 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) { switch (requestCode) {
case REQUEST_CODE_INPUT: { case REQUEST_CODE_INPUT: {
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !handleClipData(data)) { addFile(data);
addInputUri(data.getData());
}
} }
return; return;
} }
case REQUEST_CODE_OUTPUT: { case REQUEST_CODE_OUTPUT: {
// This happens after output file was selected, so start our operation // This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
mEncryptInterface.getOutputUris().clear(); mOutputUris.clear();
mEncryptInterface.getOutputUris().add(data.getData()); mOutputUris.add(data.getData());
mEncryptInterface.notifyUpdate(); startEncrypt(false);
mEncryptInterface.startEncrypt(false);
} }
return; return;
} }
@ -236,67 +565,190 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
} }
} }
@Override public static class FilesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public void onNotifyUpdate() { private Activity mActivity;
// Clear cache if needed private List<ViewModel> mDataset;
for (Uri uri : new HashSet<>(thumbnailCache.keySet())) { private View.OnClickListener mFooterOnClickListener;
if (!mEncryptInterface.getInputUris().contains(uri)) { private static final int TYPE_FOOTER = 0;
thumbnailCache.remove(uri); 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 { public ViewHolder(View itemView) {
@Override super(itemView);
public int getCount() { filename = (TextView) itemView.findViewById(R.id.filename);
return mEncryptInterface.getInputUris().size(); 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 class FooterHolder extends RecyclerView.ViewHolder {
public Object getItem(int position) { public Button mAddButton;
return mEncryptInterface.getInputUris().get(position);
public FooterHolder(View itemView) {
super(itemView);
mAddButton = (Button) itemView.findViewById(R.id.file_list_entry_add);
}
} }
@Override // Provide a suitable constructor (depends on the kind of dataset)
public long getItemId(int position) { public FilesAdapter(Activity activity, List<ViewModel> myDataset, View.OnClickListener onFooterClickListener) {
return getItem(position).hashCode(); mActivity = activity;
mDataset = myDataset;
mFooterOnClickListener = onFooterClickListener;
} }
// Create new views (invoked by the layout manager)
@Override @Override
public View getView(final int position, View convertView, ViewGroup parent) { public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Uri inputUri = mEncryptInterface.getInputUris().get(position); if (viewType == TYPE_FOOTER) {
View view; View v = LayoutInflater.from(parent.getContext())
if (convertView == null) { .inflate(R.layout.file_list_entry_add, parent, false);
view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null); return new FooterHolder(v);
} else { } 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) { // Replace the contents of a view (invoked by the layout manager)
((TextView) view.findViewById(R.id.filesize)).setText(""); @Override
} else { public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
((TextView) view.findViewById(R.id.filesize)).setText(FileHelper.readableFileSize(size)); if (holder instanceof FooterHolder) {
} FooterHolder thisHolder = (FooterHolder) holder;
view.findViewById(R.id.action_remove_file_from_list).setOnClickListener(new View.OnClickListener() { thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
@Override } else if (holder instanceof ViewHolder) {
public void onClick(View v) { ViewHolder thisHolder = (ViewHolder) holder;
delInputUri(position); // - 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 * 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 * 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.Iterator;
import java.util.List; 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; ProviderHelper mProviderHelper;
// view // view
@ -52,37 +62,43 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
private EncryptKeyCompletionView mEncryptKeyView; private EncryptKeyCompletionView mEncryptKeyView;
// model // model
private EncryptActivityInterface mEncryptInterface; private IAsymmetric mEncryptInterface;
@Override // @Override
public void onNotifyUpdate() { // public void updateUi() {
if (mSign != null) { // if (mSign != null) {
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); // 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 @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
try { try {
mEncryptInterface = (EncryptActivityInterface) activity; mEncryptInterface = (IAsymmetric) activity;
} catch (ClassCastException e) { } 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 * Inflate the layout for this fragment
*/ */
@ -94,7 +110,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() { mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
@Override @Override
public void onKeyChanged(long masterKeyId) { public void onKeyChanged(long masterKeyId) {
setSignatureKeyId(masterKeyId); mEncryptInterface.onSignatureKeyIdChanged(masterKeyId);
} }
}); });
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
@ -109,7 +125,9 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
mProviderHelper = new ProviderHelper(getActivity()); mProviderHelper = new ProviderHelper(getActivity());
// preselect keys given // 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() { mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
@Override @Override
@ -131,24 +149,20 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
/** /**
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those! * If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
*/ */
private void preselectKeys() { private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
// TODO all of this works under the assumption that the first suitable subkey is always used! if (signatureKeyId != Constants.key.none) {
// not sure if we need to distinguish between different subkeys here?
long signatureKey = mEncryptInterface.getSignatureKey();
if (signatureKey != Constants.key.none) {
try { try {
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing( CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(signatureKey)); KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
if (keyring.hasAnySecret()) { if (keyring.hasAnySecret()) {
setSignatureKeyId(keyring.getMasterKeyId()); mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId());
mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey()); mSign.setSelectedKeyId(signatureKeyId);
} }
} catch (PgpKeyNotFoundException e) { } catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e); Log.e(Constants.TAG, "key not found!", e);
} }
} }
long[] encryptionKeyIds = mEncryptInterface.getEncryptionKeys();
if (encryptionKeyIds != null) { if (encryptionKeyIds != null) {
for (long preselectedId : encryptionKeyIds) { for (long preselectedId : encryptionKeyIds) {
try { try {
@ -181,7 +195,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
for (int i = 0; i < keyIds.size(); i++) { for (int i = 0; i < keyIds.size(); i++) {
keyIdsArr[i] = iterator.next(); keyIdsArr[i] = iterator.next();
} }
setEncryptionKeyIds(keyIdsArr); mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr);
setEncryptionUserIds(userIds.toArray(new String[userIds.size()])); 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 * 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 * 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.R;
import org.sufficientlysecure.keychain.util.Passphrase; 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 mPassphrase;
private EditText mPassphraseAgain; 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 @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
try { try {
mEncryptInterface = (EncryptActivityInterface) activity; mEncryptInterface = (ISymmetric) activity;
} catch (ClassCastException e) { } 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(); p1.removeFromMemory();
p2.removeFromMemory(); p2.removeFromMemory();
if (passesEquals) { if (passesEquals) {
mEncryptInterface.setPassphrase(new Passphrase(mPassphrase.getText())); mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText()));
} else { } else {
mEncryptInterface.setPassphrase(null); mEncryptInterface.onPassphraseChanged(null);
} }
} }
}; };
@ -86,8 +103,4 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
return view; 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> * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -19,31 +19,21 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.view.Menu; import android.support.v4.app.FragmentTransaction;
import android.view.MenuItem; import android.view.View;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents; import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.ui.base.BaseActivity;
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.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ShareHelper;
import java.util.ArrayList; public class EncryptTextActivity extends BaseActivity implements
import java.util.HashSet; EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
import java.util.Set; EncryptTextFragment.IMode {
public class EncryptTextActivity extends EncryptActivity implements EncryptActivityInterface {
/* Intents */ /* Intents */
public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT; 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_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"; public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
// view Fragment mModeFragment;
private int mCurrentMode = MODE_ASYMMETRIC; EncryptTextFragment mEncryptFragment;
// 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;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// if called with an intent action, do not init drawer navigation setFullScreenDialogClose(new View.OnClickListener() {
if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) { @Override
// lock drawer public void onClick(View v) {
// deactivateDrawerNavigation(); finish();
// TODO: back button to key? }
} else { }, false);
// activateDrawerNavigation(savedInstanceState);
}
// Handle intent actions // Handle intent actions
handleActions(getIntent()); handleActions(getIntent(), savedInstanceState);
updateModeFragment();
} }
@Override @Override
@ -341,58 +68,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
setContentView(R.layout.encrypt_text_activity); 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 * Handles all actions with this intent
* *
* @param intent * @param intent
*/ */
private void handleActions(Intent intent) { private void handleActions(Intent intent, Bundle savedInstanceState) {
String action = intent.getAction(); String action = intent.getAction();
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
String type = intent.getType(); String type = intent.getType();
@ -423,19 +105,61 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
} }
String textData = extras.getString(EXTRA_TEXT); 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 // preselect keys given by intent
mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
/**
* Main Actions if (savedInstanceState == null) {
*/ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (ACTION_ENCRYPT_TEXT.equals(action) && textData != null) {
mMessage = textData; mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
} else if (ACTION_ENCRYPT_TEXT.equals(action)) { transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
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 * 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 * it under the terms of the GNU General Public License as published by
@ -18,32 +18,102 @@
package org.sufficientlysecure.keychain.ui; package org.sufficientlysecure.keychain.ui;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle; 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.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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"; 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 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 @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
try { try {
mEncryptInterface = (EncryptActivityInterface) activity; mModeInterface = (IMode) activity;
} catch (ClassCastException e) { } 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 @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
mEncryptInterface.setMessage(s.toString()); mMessage = s.toString();
} }
}); });
// set initial text
if (mMessage != null) {
mText.setText(mMessage);
}
return view; return view;
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mMessage = getArguments().getString(ARG_TEXT);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onActivityCreated(savedInstanceState); super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.encrypt_text_fragment, menu);
String text = mEncryptInterface.getMessage();
if (text != null) {
mText.setText(text);
}
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.isCheckable()) {
item.setChecked(!item.isChecked());
}
switch (item.getItemId()) { 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: { case R.id.encrypt_copy: {
mEncryptInterface.startEncrypt(false); startEncrypt(false);
break; break;
} }
case R.id.encrypt_share: { case R.id.encrypt_share: {
mEncryptInterface.startEncrypt(true); startEncrypt(true);
break; break;
} }
default: { default: {
@ -109,4 +198,189 @@ public class EncryptTextFragment extends Fragment {
} }
return true; 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.R;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
public class HelpActivity extends BaseActivity { public class HelpActivity extends BaseActivity {
public static final String EXTRA_SELECTED_TAB = "selected_tab"; 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.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult; 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.CloudImportService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
@ -47,7 +48,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; 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 = 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 = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = 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 public class ImportKeysListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
private static final String ARG_DATA_URI = "uri"; private static final String ARG_DATA_URI = "uri";
private static final String ARG_BYTES = "bytes"; 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 Activity mActivity;
private ImportKeysAdapter mAdapter; private ImportKeysAdapter mAdapter;
@ -66,6 +68,7 @@ public class ImportKeysListFragment extends ListFragment implements
private static final int LOADER_ID_CLOUD = 1; private static final int LOADER_ID_CLOUD = 1;
private LongSparseArray<ParcelableKeyRing> mCachedKeyData; private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
private boolean mNonInteractive;
public LoaderState getLoaderState() { public LoaderState getLoaderState() {
return mLoaderState; 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) { 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(); ImportKeysListFragment frag = new ImportKeysListFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putByteArray(ARG_BYTES, bytes); args.putByteArray(ARG_BYTES, bytes);
args.putParcelable(ARG_DATA_URI, dataUri); args.putParcelable(ARG_DATA_URI, dataUri);
args.putString(ARG_SERVER_QUERY, serverQuery); args.putString(ARG_SERVER_QUERY, serverQuery);
args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
frag.setArguments(args); frag.setArguments(args);
@ -173,9 +179,11 @@ public class ImportKeysListFragment extends ListFragment implements
mAdapter = new ImportKeysAdapter(mActivity); mAdapter = new ImportKeysAdapter(mActivity);
setListAdapter(mAdapter); setListAdapter(mAdapter);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); Bundle args = getArguments();
byte[] bytes = getArguments().getByteArray(ARG_BYTES); Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null;
String query = getArguments().getString(ARG_SERVER_QUERY); 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) { if (dataUri != null || bytes != null) {
mLoaderState = new BytesLoaderState(bytes, dataUri); 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) { public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id); super.onListItemClick(l, v, position, id);
if (mNonInteractive) {
return;
}
// Select checkbox! // Select checkbox!
// Update underlying data and notify adapter of change. The adapter will // Update underlying data and notify adapter of change. The adapter will
// update the view automatically. // update the view automatically.

View File

@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri; import android.net.Uri;
import android.nfc.NdefMessage; import android.nfc.NdefMessage;
import android.nfc.NfcAdapter; import android.nfc.NfcAdapter;
@ -83,25 +84,22 @@ public class ImportKeysProxyActivity extends FragmentActivity {
returnResult = false; returnResult = false;
processScannedContent(dataUri); processScannedContent(dataUri);
} else if (ACTION_SCAN_IMPORT.equals(action)) { } else if (ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
returnResult = false; returnResult = false;
IntentIntegrator integrator = new IntentIntegrator(this); IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text)) .setPrompt(getString(R.string.import_qr_code_text))
.setResultDisplayDuration(0) .setResultDisplayDuration(0);
.initiateScan(); integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
integrator.initiateScan();
} else if (ACTION_SCAN_WITH_RESULT.equals(action)) { } else if (ACTION_SCAN_WITH_RESULT.equals(action)) {
returnResult = true; returnResult = true;
IntentIntegrator integrator = new IntentIntegrator(this); IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text)) .setPrompt(getString(R.string.import_qr_code_text))
.setResultDisplayDuration(0) .setResultDisplayDuration(0);
.initiateScan(); integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (ACTION_QR_CODE_API.equals(action)) { integrator.initiateScan();
// scan using xzing's Barcode Scanner from outside OpenKeychain
returnResult = false;
new IntentIntegrator(this).initiateScan();
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { } else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
// Check to see if the Activity started due to an Android Beam // Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 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 android.view.View;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
public class LogDisplayActivity extends 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.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; 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.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; 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. * This activity encapsulates a DialogFragment to emulate a dialog.
*/ */
public class PassphraseDialogActivity extends FragmentActivity { 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"; public static final String EXTRA_SUBKEY_ID = "secret_key_id";
// special extra for OpenPgpService // 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; 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) // 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); show(this, keyId, serviceIntent);
} }
@ -141,7 +163,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
PassphraseDialogFragment frag = new PassphraseDialogFragment(); PassphraseDialogFragment frag = new PassphraseDialogFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong(EXTRA_SUBKEY_ID, keyId); args.putLong(EXTRA_SUBKEY_ID, keyId);
args.putParcelable(EXTRA_DATA, serviceIntent); args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
frag.setArguments(args); frag.setArguments(args);
@ -174,11 +196,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
R.style.Theme_AppCompat_Light_Dialog); R.style.Theme_AppCompat_Light_Dialog);
mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID); mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID);
mServiceIntent = getArguments().getParcelable(EXTRA_DATA); mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); 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); LayoutInflater inflater = LayoutInflater.from(theme);
View view = inflater.inflate(R.layout.passphrase_dialog, null); View view = inflater.inflate(R.layout.passphrase_dialog, null);
@ -243,8 +266,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
case PASSPHRASE_EMPTY: case PASSPHRASE_EMPTY:
finishCaching(new Passphrase("")); finishCaching(new Passphrase(""));
default: default:
message = "This should not happen!"; throw new AssertionError("Unhandled SecretKeyType (should not happen)");
break;
} }
} catch (ProviderHelper.NotFoundException e) { } 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.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
mPassphraseEditText.setOnEditorActionListener(this); 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); mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
} else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { } else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
@ -309,7 +331,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
AlertDialog dialog = alert.create(); AlertDialog dialog = alert.create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE, 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; return dialog;
} }
@ -406,17 +428,14 @@ public class PassphraseDialogActivity extends FragmentActivity {
return; return;
} }
CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase);
if (mServiceIntent != null) { if (mServiceIntent != null) {
// TODO: Not routing passphrase through OpenPGP API currently CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel);
// due to security concerns...
// BUT this means you need to _cache_ passphrases!
getActivity().setResult(RESULT_OK, mServiceIntent); getActivity().setResult(RESULT_OK, mServiceIntent);
} else { } else {
// also return passphrase back to activity // also return passphrase back to activity
Intent returnIntent = new Intent(); Intent returnIntent = new Intent();
returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase); returnIntent.putExtra(RESULT_CRYPTO_INPUT, inputParcel);
returnIntent.putExtra(EXTRA_KEY_ID, mSecretRing.getMasterKeyId());
returnIntent.putExtra(EXTRA_SUBKEY_ID, mSubKeyId);
getActivity().setResult(RESULT_OK, returnIntent); 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.R;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style; 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.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;

View File

@ -107,10 +107,10 @@ public class SettingsActivity extends PreferenceActivity {
values[i] = "" + valueIds[i]; values[i] = "" + valueIds[i];
} }
initializeUseDefaultYubikeyPin( initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN)); (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
initializeUseNumKeypadForYubikeyPin( initializeUseNumKeypadForYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
} }
@ -262,10 +262,10 @@ public class SettingsActivity extends PreferenceActivity {
values[i] = "" + valueIds[i]; values[i] = "" + valueIds[i];
} }
initializeUseDefaultYubikeyPin( initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN)); (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
initializeUseNumKeypadForYubikeyPin( initializeUseNumKeypadForYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); (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(); return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver();
} }
private static void initializeUseDefaultYubikeyPin(final CheckBoxPreference mUseDefaultYubikeyPin) { private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) {
mUseDefaultYubikeyPin.setChecked(sPreferences.useDefaultYubikeyPin()); mUseDefaultYubiKeyPin.setChecked(sPreferences.useDefaultYubiKeyPin());
mUseDefaultYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { mUseDefaultYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
mUseDefaultYubikeyPin.setChecked((Boolean) newValue); mUseDefaultYubiKeyPin.setChecked((Boolean) newValue);
sPreferences.setUseDefaultYubikeyPin((Boolean) newValue); sPreferences.setUseDefaultYubiKeyPin((Boolean) newValue);
return false; return false;
} }
}); });
} }
private static void initializeUseNumKeypadForYubikeyPin(final CheckBoxPreference mUseNumKeypadForYubikeyPin) { private static void initializeUseNumKeypadForYubiKeyPin(final CheckBoxPreference mUseNumKeypadForYubiKeyPin) {
mUseNumKeypadForYubikeyPin.setChecked(sPreferences.useNumKeypadForYubikeyPin()); mUseNumKeypadForYubiKeyPin.setChecked(sPreferences.useNumKeypadForYubiKeyPin());
mUseNumKeypadForYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { mUseNumKeypadForYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
mUseNumKeypadForYubikeyPin.setChecked((Boolean) newValue); mUseNumKeypadForYubiKeyPin.setChecked((Boolean) newValue);
sPreferences.setUseNumKeypadForYubikeyPin((Boolean) newValue); sPreferences.setUseNumKeypadForYubiKeyPin((Boolean) newValue);
return false; return false;
} }
}); });

View File

@ -27,6 +27,7 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.R; 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;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; 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;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log; 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.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; 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.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log; 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.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; 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;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.service.PassphraseCacheService; 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.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.Notify; 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.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.ExportHelper; 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.NfcHelper;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public class ViewKeyActivity extends BaseActivity implements public class ViewKeyActivity extends BaseNfcActivity implements
LoaderManager.LoaderCallbacks<Cursor> { 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_QR_FINGERPRINT = 1;
static final int REQUEST_DELETE = 2; static final int REQUEST_DELETE = 2;
static final int REQUEST_EXPORT = 3; static final int REQUEST_EXPORT = 3;
public static final String EXTRA_DISPLAY_RESULT = "display_result";
ExportHelper mExportHelper; ExportHelper mExportHelper;
ProviderHelper mProviderHelper; ProviderHelper mProviderHelper;
@ -108,6 +119,8 @@ public class ViewKeyActivity extends BaseActivity implements
private ImageView mQrCode; private ImageView mQrCode;
private CardView mQrCodeLayout; private CardView mQrCodeLayout;
private String mQrCodeLoaded;
// NFC // NFC
private NfcHelper mNfcHelper; private NfcHelper mNfcHelper;
@ -257,6 +270,21 @@ public class ViewKeyActivity extends BaseActivity implements
mNfcHelper = new NfcHelper(this, mProviderHelper); mNfcHelper = new NfcHelper(this, mProviderHelper);
mNfcHelper.initNfc(mDataUri); 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 @Override
@ -264,6 +292,35 @@ public class ViewKeyActivity extends BaseActivity implements
setContentView(R.layout.view_key_activity); 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 @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(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) { private void encrypt(Uri dataUri, boolean text) {
// If there is no encryption key, don't bother. // If there is no encryption key, don't bother.
if (!mHasEncrypt) { if (!mHasEncrypt) {
@ -638,6 +761,7 @@ public class ViewKeyActivity extends BaseActivity implements
} }
protected void onPostExecute(Bitmap qrCode) { protected void onPostExecute(Bitmap qrCode) {
mQrCodeLoaded = fingerprint;
// scale the image up to our actual size. we do this in code rather // 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. // than let the ImageView do this because we don't require filtering.
Bitmap scaled = Bitmap.createScaledBitmap(qrCode, 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 // Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.) // old cursor once we return.)
switch (loader.getId()) { switch (loader.getId()) {
case LOADER_ID_UNIFIED: { case LOADER_ID_UNIFIED: {
// if there is no data, just break
if (!data.moveToFirst()) { 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 // get name, email, and comment from USER_ID
KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
if (mainUserId.name != null) { if (mainUserId.name != null) {
@ -732,6 +844,15 @@ public class ViewKeyActivity extends BaseActivity implements
mName.setText(R.string.user_id_no_name); 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 the refresh animation isn't playing
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) { if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
// re-create options menu based on mIsSecret, mIsVerified // re-create options menu based on mIsSecret, mIsVerified
@ -768,7 +889,6 @@ public class ViewKeyActivity extends BaseActivity implements
} else if (mIsExpired) { } else if (mIsExpired) {
if (mIsSecret) { if (mIsSecret) {
mStatusText.setText(R.string.view_key_expired_secret); mStatusText.setText(R.string.view_key_expired_secret);
mName.setText(mainUserId.name);
} else { } else {
mStatusText.setText(R.string.view_key_expired); mStatusText.setText(R.string.view_key_expired);
} }
@ -787,7 +907,7 @@ public class ViewKeyActivity extends BaseActivity implements
mStatusImage.setVisibility(View.GONE); mStatusImage.setVisibility(View.GONE);
color = getResources().getColor(R.color.primary); color = getResources().getColor(R.color.primary);
// reload qr code only if the fingerprint changed // reload qr code only if the fingerprint changed
if (!mFingerprint.equals(oldFingerprint)) { if (!mFingerprint.equals(mQrCodeLoaded)) {
loadQrCode(mFingerprint); loadQrCode(mFingerprint);
} }
photoTask.execute(mMasterKeyId); photoTask.execute(mMasterKeyId);
@ -873,6 +993,7 @@ public class ViewKeyActivity extends BaseActivity implements
mStatusImage.setAlpha(80); mStatusImage.setAlpha(80);
break; 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.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; 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.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.ExportHelper;

View File

@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v7.widget.CardView; import android.support.v7.widget.CardView;
import android.transition.Fade; import android.transition.Fade;
@ -48,11 +49,11 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; 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.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment; import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener; 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.ContactHelper;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -62,21 +63,26 @@ public class ViewKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri"; 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 mUserIds;
//private ListView mLinkedSystemContact; //private ListView mLinkedSystemContact;
boolean mIsSecret = false; boolean mIsSecret = false;
boolean mSystemContactLoaded = false;
CardView mSystemContactCard;
LinearLayout mSystemContactLayout; LinearLayout mSystemContactLayout;
ImageView mSystemContactPicture; ImageView mSystemContactPicture;
TextView mSystemContactName; TextView mSystemContactName;
private static final int LOADER_ID_USER_IDS = 0; private static final int LOADER_ID_UNIFIED = 0;
private static final int LOADER_ID_LINKED_IDS = 1; 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 UserIdsAdapter mUserIdsAdapter;
private LinkedIdsAdapter mLinkedIdsAdapter; private LinkedIdsAdapter mLinkedIdsAdapter;
@ -90,35 +96,16 @@ public class ViewKeyFragment extends LoaderFragment implements
/** /**
* Creates new instance of this fragment * 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(); ViewKeyFragment frag = new ViewKeyFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri); args.putParcelable(ARG_DATA_URI, dataUri);
args.putBoolean(ARG_IS_SECRET, isSecret);
args.putByteArray(ARG_FINGERPRINT, fingerprint);
frag.setArguments(args); frag.setArguments(args);
return frag; 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 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, 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); mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout);
mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name); mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name);
mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture); 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 * Hides card if no linked system contact exists. Sets name, picture
* and onClickListener for the linked system contact's layout * and onClickListener for the linked system contact's layout.
* In the case of a secret key, "me" contact details are loaded * In the case of a secret key, "me" (own profile) contact details are loaded.
*
* @param masterKeyId
*/ */
private void loadLinkedSystemContact(final long masterKeyId) { private void loadLinkedSystemContact(final long contactId) {
final Context context = mSystemContactName.getContext(); final Context context = mSystemContactName.getContext();
final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver();
long contactId;
String contactName = null; String contactName = null;
if (mIsSecret) {//all secret keys are linked to "me" profile in contacts if (mIsSecret) {//all secret keys are linked to "me" profile in contacts
contactId = ContactHelper.getMainProfileContactId(resolver);
List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context); List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context);
if (mainProfileNames != null && mainProfileNames.size() > 0) { if (mainProfileNames != null && mainProfileNames.size() > 0) {
contactName = mainProfileNames.get(0); contactName = mainProfileNames.get(0);
} }
} else { } else {
contactId = ContactHelper.findContactId(resolver, masterKeyId);
contactName = ContactHelper.getContactName(resolver, contactId); contactName = ContactHelper.getContactName(resolver, contactId);
} }
if (contactName != null) {//contact name exists for given master key if (contactName != null) {//contact name exists for given master key
showLinkedSystemContact();
mSystemContactName.setText(contactName); mSystemContactName.setText(contactName);
Bitmap picture; Bitmap picture;
if (mIsSecret) { if (mIsSecret) {
picture = ContactHelper.loadMainProfilePhoto(resolver, false); picture = ContactHelper.loadMainProfilePhoto(resolver, false);
} else { } else {
picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, false); picture = ContactHelper.loadPhotoByContactId(resolver, contactId, false);
} }
if (picture != null) mSystemContactPicture.setImageBitmap(picture); if (picture != null) mSystemContactPicture.setImageBitmap(picture);
final long finalContactId = contactId;
mSystemContactLayout.setOnClickListener(new View.OnClickListener() { mSystemContactLayout.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { 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 * 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 * contactId (CONTACT_ID column from ContactsContract.RawContact table which is _ID column in
* ContactsContract.Contact table) * ContactsContract.Contact table)
* *
* @param contactId _ID for row in ContactsContract.Contacts table * @param contactId _ID for row in ContactsContract.Contacts table
* @param context
*/ */
private void launchContactActivity(final long contactId, Context context) { private void launchContactActivity(final long contactId, Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
@ -271,23 +263,61 @@ public class ViewKeyFragment extends LoaderFragment implements
context.startActivity(intent); 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; mDataUri = dataUri;
mIsSecret = isSecret;
mFingerprint = fingerprint;
Log.i(Constants.TAG, "mDataUri: " + mDataUri); Log.i(Constants.TAG, "mDataUri: " + mDataUri);
// load user ids after we know if it's a secret key // Prepare the loaders. Either re-connect with an existing ones,
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null); // or start new ones.
mUserIds.setAdapter(mUserIdsAdapter); getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
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);
} }
@Override @Override
@ -295,11 +325,45 @@ public class ViewKeyFragment extends LoaderFragment implements
setContentShown(false); setContentShown(false);
switch (id) { switch (id) {
case LOADER_ID_USER_IDS: case LOADER_ID_UNIFIED: {
return UserIdsAdapter.createLoader(getActivity(), mDataUri); 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); 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: default:
return null; return null;
@ -307,21 +371,71 @@ public class ViewKeyFragment extends LoaderFragment implements
} }
@Override @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 // Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.) // old cursor once we return.)
switch (loader.getId()) { 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: { case LOADER_ID_USER_IDS: {
mUserIdsAdapter.swapCursor(cursor); mUserIdsAdapter.swapCursor(data);
loadLinkedSystemContact(KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint));
break; break;
} }
case LOADER_ID_LINKED_IDS: { case LOADER_ID_LINKED_IDS: {
mLinkedIdsCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE); mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE);
mLinkedIdsAdapter.swapCursor(cursor); mLinkedIdsAdapter.swapCursor(data);
break; 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); 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); mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) { if (mSlingerButton.hasOnClickListeners()) {
mSlinger.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.VISIBLE);
} else {
mSlinger.setVisibility(View.GONE);
} }
mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
mMainUserIdRest.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/>. * 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.app.Activity;
import android.os.Bundle; 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 * 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 * see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
*/ */
protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener, public void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
View.OnClickListener cancelOnClickListener) { View.OnClickListener cancelOnClickListener) {
setActionBarIcon(R.drawable.ic_close_white_24dp); setActionBarIcon(R.drawable.ic_close_white_24dp);
// Inflate the custom action bar view // 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.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.nfc.NfcAdapter; import android.nfc.NfcAdapter;
import android.nfc.Tag; import android.nfc.Tag;
import android.nfc.tech.IsoDep; import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.util.encoders.Hex; import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; 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.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException; public abstract class BaseNfcActivity extends BaseActivity {
import java.nio.ByteBuffer;
/** public static final int REQUEST_CODE_PASSPHRASE = 1;
* 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;
protected Passphrase mPin;
private NfcAdapter mNfcAdapter; private NfcAdapter mNfcAdapter;
private IsoDep mIsoDep; private IsoDep mIsoDep;
private String mAction;
private String mPin; private static final int TIMEOUT = 100000;
private Long mKeyId;
// sign
private byte[] mHashToSign;
private int mHashAlgo;
// decrypt
private byte[] mEncryptedSessionKey;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Log.d(Constants.TAG, "NfcActivity.onCreate");
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Intent intent = getIntent(); Intent intent = getIntent();
Bundle data = intent.getExtras();
String action = intent.getAction(); String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
// if we get are passed a key id, save it for the check throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!");
if (data.containsKey(EXTRA_KEY_ID)) {
mKeyId = data.getLong(EXTRA_KEY_ID);
} }
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); * This activity is started as a singleTop activity.
Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign)); * All new NFC Intents which are delivered to this activity are handled here
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString()); */
break; @Override
case ACTION_DECRYPT_SESSION_KEY: public void onNewIntent(Intent intent) {
mAction = action; if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
mPin = data.getString(EXTRA_PIN); try {
mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY); handleNdefDiscoveredIntent(intent);
mServiceIntent = data.getParcelable(EXTRA_DATA); } catch (IOException e) {
handleNfcError(e);
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;
} }
} }
@Override public void handleNfcError(IOException e) {
protected void initLayout() {
setContentView(R.layout.nfc_activity); 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() { public void onPause() {
super.onPause(); super.onPause();
Log.d(Constants.TAG, "NfcActivity.onPause"); Log.d(Constants.TAG, "BaseNfcActivity.onPause");
disableNfcForegroundDispatch(); disableNfcForegroundDispatch();
} }
@ -145,25 +119,45 @@ public class NfcActivity extends BaseActivity {
*/ */
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Log.d(Constants.TAG, "NfcActivity.onResume"); Log.d(Constants.TAG, "BaseNfcActivity.onResume");
enableNfcForegroundDispatch(); enableNfcForegroundDispatch();
} }
/** protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) {
* This activity is started as a singleTop activity.
* All new NFC Intents which are delivered to this activity are handled here Preferences prefs = Preferences.getPreferences(this);
*/ if (prefs.useDefaultYubiKeyPin()) {
public void onNewIntent(Intent intent) { mPin = new Passphrase("123456");
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { return;
try { }
handleNdefDiscoveredIntent(intent);
} catch (IOException e) { Intent intent = new Intent(this, PassphraseDialogActivity.class);
Log.e(Constants.TAG, "Connection error!", e); intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
toast("Connection Error: " + e.getMessage()); RequiredInputParcel.createRequiredPassphrase(requiredInput));
setResult(RESULT_CANCELED, mServiceIntent); startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
finish();
} }
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. * 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); 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) // Command APDU (page 51) for SELECT FILE command (page 29)
String opening = String opening =
"00" // CLA "00" // CLA
+ "A4" // INS + "A4" // INS
+ "04" // P1 + "04" // P1
+ "00" // P2 + "00" // P2
+ "06" // Lc (number of bytes) + "06" // Lc (number of bytes)
+ "D27600012401" // Data (6 bytes) + "D27600012401" // Data (6 bytes)
+ "00"; // Le + "00"; // Le
if ( ! card(opening).equals(accepted)) { // activate connection if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection
toast("Opening Error!"); throw new IOException("Initialization failed!");
setResult(RESULT_CANCELED, mServiceIntent);
finish();
return;
} }
// Command APDU for VERIFY command (page 32) onNfcPerform();
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;
}
if (ACTION_SIGN_HASH.equals(mAction)) { mIsoDep.close();
mIsoDep = null;
// 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();
}
} }
/** protected void onNfcPerform() throws IOException {
* Gets the user ID
* final byte[] nfcFingerprints = nfcGetFingerprints();
* @return the user id as "name <email>" final String nfcUserId = nfcGetUserId();
* @throws java.io.IOException final byte[] nfcAid = nfcGetAid();
*/
public String getUserId() throws IOException { final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
String info = "00CA006500";
String data = "00CA005E00"; try {
return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">"; 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 /** 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. * @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. * @return The long key id of the requested key, or null if not found.
*/ */
public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException { public Long nfcGetKeyId(int idx) throws IOException {
byte[] fp = nfcGetFingerprint(isoDep, idx); byte[] fp = nfcGetFingerprint(idx);
if (fp == null) { if (fp == null) {
return null; return null;
} }
@ -318,9 +263,9 @@ public class NfcActivity extends BaseActivity {
* *
* @return The fingerprints of all subkeys in a contiguous byte array. * @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"; String data = "00CA006E00";
byte[] buf = isoDep.transceive(Hex.decode(data)); byte[] buf = mIsoDep.transceive(Hex.decode(data));
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint()); 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. * @param idx Index of the key to return the fingerprint from.
* @return The fingerprint of the requested key, or null if not found. * @return The fingerprint of the requested key, or null if not found.
*/ */
public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException { public byte[] nfcGetFingerprint(int idx) throws IOException {
byte[] data = nfcGetFingerprints(isoDep); byte[] data = nfcGetFingerprints();
// return the master key fingerprint // return the master key fingerprint
ByteBuffer fpbuf = ByteBuffer.wrap(data); ByteBuffer fpbuf = ByteBuffer.wrap(data);
byte[] fp = new byte[20]; byte[] fp = new byte[20];
fpbuf.position(idx*20); fpbuf.position(idx * 20);
fpbuf.get(fp, 0, 20); fpbuf.get(fp, 0, 20);
return fp; 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 * Calls to calculate the signature and returns the MPI value
* *
* @param hash the hash for signing * @param hash the hash for signing
* @return a big integer representing the MPI for the given hash * @return a big integer representing the MPI for the given hash
* @throws java.io.IOException
*/ */
public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing)
// dsi, including Lc // dsi, including Lc
String dsi; String dsi;
@ -367,7 +325,7 @@ public class NfcActivity extends BaseActivity {
switch (hashAlgo) { switch (hashAlgo) {
case HashAlgorithmTags.SHA1: case HashAlgorithmTags.SHA1:
if (hash.length != 20) { 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 dsi = "23" // Lc
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
@ -378,45 +336,45 @@ public class NfcActivity extends BaseActivity {
break; break;
case HashAlgorithmTags.RIPEMD160: case HashAlgorithmTags.RIPEMD160:
if (hash.length != 20) { 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); dsi = "233021300906052B2403020105000414" + getHex(hash);
break; break;
case HashAlgorithmTags.SHA224: case HashAlgorithmTags.SHA224:
if (hash.length != 28) { 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); dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
break; break;
case HashAlgorithmTags.SHA256: case HashAlgorithmTags.SHA256:
if (hash.length != 32) { 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); dsi = "333031300D060960864801650304020105000420" + getHex(hash);
break; break;
case HashAlgorithmTags.SHA384: case HashAlgorithmTags.SHA384:
if (hash.length != 48) { 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); dsi = "433041300D060960864801650304020205000430" + getHex(hash);
break; break;
case HashAlgorithmTags.SHA512: case HashAlgorithmTags.SHA512:
if (hash.length != 64) { 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); dsi = "533051300D060960864801650304020305000440" + getHex(hash);
break; break;
default: 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) // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
String apdu = String apdu =
"002A9E9A" // CLA, INS, P1, P2 "002A9E9A" // CLA, INS, P1, P2
+ dsi // digital signature input + dsi // digital signature input
+ "00"; // Le + "00"; // Le
String response = card(apdu); String response = nfcCommunicate(apdu);
// split up response into signature and status // split up response into signature and status
String status = response.substring(response.length()-4); String status = response.substring(response.length()-4);
@ -426,22 +384,20 @@ public class NfcActivity extends BaseActivity {
while (status.substring(0, 2).equals("61")) { while (status.substring(0, 2).equals("61")) {
Log.d(Constants.TAG, "requesting more data, status " + status); Log.d(Constants.TAG, "requesting more data, status " + status);
// Send GET RESPONSE command // Send GET RESPONSE command
response = card("00C00000" + status.substring(2)); response = nfcCommunicate("00C00000" + status.substring(2));
status = response.substring(response.length()-4); status = response.substring(response.length()-4);
signature += response.substring(0, response.length()-4); signature += response.substring(0, response.length()-4);
} }
Log.d(Constants.TAG, "final response:" + status); Log.d(Constants.TAG, "final response:" + status);
if ( ! status.equals("9000")) { if ( ! "9000".equals(status)) {
toast("Bad NFC response code: " + status); throw new IOException("Bad NFC response code: " + status);
return null;
} }
// Make sure the signature we received is actually the expected number of bytes long! // Make sure the signature we received is actually the expected number of bytes long!
if (signature.length() != 256 && signature.length() != 512) { if (signature.length() != 256 && signature.length() != 512) {
toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2); throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
return null;
} }
return Hex.decode(signature); return Hex.decode(signature);
@ -452,9 +408,10 @@ public class NfcActivity extends BaseActivity {
* *
* @param encryptedSessionKey the encoded session key * @param encryptedSessionKey the encoded session key
* @return the decoded session key * @return the decoded session key
* @throws java.io.IOException
*/ */
public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption)
String firstApdu = "102a8086fe"; String firstApdu = "102a8086fe";
String secondApdu = "002a808603"; String secondApdu = "002a808603";
String le = "00"; String le = "00";
@ -468,22 +425,48 @@ public class NfcActivity extends BaseActivity {
two[i] = encryptedSessionKey[i + one.length + 1]; two[i] = encryptedSessionKey[i + one.length + 1];
} }
String first = card(firstApdu + getHex(one)); String first = nfcCommunicate(firstApdu + getHex(one));
String second = card(secondApdu + getHex(two) + le); String second = nfcCommunicate(secondApdu + getHex(two) + le);
String decryptedSessionKey = getDataField(second); String decryptedSessionKey = nfcGetDataField(second);
Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey);
return Hex.decode(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 * Prints a message to the screen
* *
* @param text the text which should be contained within the toast * @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(); Toast.makeText(this, text, Toast.LENGTH_LONG).show();
} }
@ -493,7 +476,7 @@ public class NfcActivity extends BaseActivity {
*/ */
public void enableNfcForegroundDispatch() { public void enableNfcForegroundDispatch() {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this); 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); .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
IntentFilter[] writeTagFilters = new IntentFilter[]{ IntentFilter[] writeTagFilters = new IntentFilter[]{
@ -518,13 +501,7 @@ public class NfcActivity extends BaseActivity {
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!"); Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
} }
/** public String nfcGetHolderName(String name) {
* 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) {
String slength; String slength;
int ilength; int ilength;
name = name.substring(6); name = name.substring(6);
@ -535,34 +512,16 @@ public class NfcActivity extends BaseActivity {
return (name); return (name);
} }
/** private String nfcGetDataField(String output) {
* 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) {
return output.substring(0, output.length() - 4); return output.substring(0, output.length() - 4);
} }
/** public String nfcCommunicate(String apdu) throws IOException {
* Communicates with the OpenPgpCard via the APDU return getHex(mIsoDep.transceive(Hex.decode(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)));
} }
/**
* 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) { public static String getHex(byte[] raw) {
return new String(Hex.encode(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