Merge branch 'master' into mime4j
Conflicts: OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java OpenKeychain/src/main/res/values/strings.xml
@@ -1,6 +1,6 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw
|
lang_map = he: iw, zh_TW: zh-rTW
|
||||||
|
|
||||||
[open-keychain.strings]
|
[open-keychain.strings]
|
||||||
file_filter = OpenKeychain/src/main/res/values-<lang>/strings.xml
|
file_filter = OpenKeychain/src/main/res/values-<lang>/strings.xml
|
||||||
|
|||||||
1
Graphics/drawables/account_key.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M11,10V12H10V14H8V12H5.83C5.42,13.17 4.31,14 3,14A3,3 0 0,1 0,11A3,3 0 0,1 3,8C4.31,8 5.42,8.83 5.83,10H11M3,10A1,1 0 0,0 2,11A1,1 0 0,0 3,12A1,1 0 0,0 4,11A1,1 0 0,0 3,10M16,14C18.67,14 24,15.34 24,18V20H8V18C8,15.34 13.33,14 16,14M16,12A4,4 0 0,1 12,8A4,4 0 0,1 16,4A4,4 0 0,1 20,8A4,4 0 0,1 16,12Z" /></svg>
|
||||||
|
After Width: | Height: | Size: 595 B |
1
Graphics/drawables/link.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,6H13V7.9H16C18.26,7.9 20.1,9.73 20.1,12A4.1,4.1 0 0,1 16,16.1H13V18H16A6,6 0 0,0 22,12C22,8.68 19.31,6 16,6M3.9,12C3.9,9.73 5.74,7.9 8,7.9H11V6H8A6,6 0 0,0 2,12A6,6 0 0,0 8,18H11V16.1H8C5.74,16.1 3.9,14.26 3.9,12M8,13H16V11H8V13Z" /></svg>
|
||||||
|
After Width: | Height: | Size: 528 B |
4
Graphics/drawables/linked_dns.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M7,9A2,2 0 0,1 5,7A2,2 0 0,1 7,5A2,2 0 0,1 9,7A2,2 0 0,1 7,9M20,3H4A1,1 0 0,0 3,4V10A1,1 0 0,0 4,11H20A1,1 0 0,0 21,10V4A1,1 0 0,0 20,3M7,19A2,2 0 0,1 5,17A2,2 0 0,1 7,15A2,2 0 0,1 9,17A2,2 0 0,1 7,19M20,13H4A1,1 0 0,0 3,14V20A1,1 0 0,0 4,21H20A1,1 0 0,0 21,20V14A1,1 0 0,0 20,13Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 401 B |
4
Graphics/drawables/linked_github.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 852 B |
4
Graphics/drawables/linked_https.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M16.36,14C16.44,13.34 16.5,12.68 16.5,12C16.5,11.32 16.44,10.66 16.36,10H19.74C19.9,10.64 20,11.31 20,12C20,12.69 19.9,13.36 19.74,14M14.59,19.56C15.19,18.45 15.65,17.25 15.97,16H18.92C17.96,17.65 16.43,18.93 14.59,19.56M14.34,14H9.66C9.56,13.34 9.5,12.68 9.5,12C9.5,11.32 9.56,10.65 9.66,10H14.34C14.43,10.65 14.5,11.32 14.5,12C14.5,12.68 14.43,13.34 14.34,14M12,19.96C11.17,18.76 10.5,17.43 10.09,16H13.91C13.5,17.43 12.83,18.76 12,19.96M8,8H5.08C6.03,6.34 7.57,5.06 9.4,4.44C8.8,5.55 8.35,6.75 8,8M5.08,16H8C8.35,17.25 8.8,18.45 9.4,19.56C7.57,18.93 6.03,17.65 5.08,16M4.26,14C4.1,13.36 4,12.69 4,12C4,11.31 4.1,10.64 4.26,10H7.64C7.56,10.66 7.5,11.32 7.5,12C7.5,12.68 7.56,13.34 7.64,14M12,4.03C12.83,5.23 13.5,6.57 13.91,8H10.09C10.5,6.57 11.17,5.23 12,4.03M18.92,8H15.97C15.65,6.75 15.19,5.55 14.59,4.44C16.43,5.07 17.96,6.34 18.92,8M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
4
Graphics/drawables/linked_twitter.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 763 B |
3
Graphics/drawables/octo_link.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg height="1024" width="1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M256 576h64v64h-64c-96 0-192-108-192-224s99-224 192-224h256c93 0 192 108 192 224 0 90-58 174-128 208v-74c37-29 64-81 64-134 0-82-65-160-128-160H256c-63 0-128 78-128 160s64 160 128 160z m576-192h-64v64h64c64 0 128 78 128 160s-65 160-128 160H576c-63 0-128-78-128-160 0-53 27-105 64-134v-74c-70 34-128 118-128 208 0 116 99 224 192 224h256c93 0 192-108 192-224s-96-224-192-224z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 464 B |
77
Graphics/drawables/status_signature_verified_inner.svg
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="100px"
|
||||||
|
height="100px"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
inkscape:version="0.48.5 r10040"
|
||||||
|
sodipodi:docname="status_signature_verified_cutout.svg">
|
||||||
|
<metadata
|
||||||
|
id="metadata16">
|
||||||
|
<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>signature-verified-cutout</dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1280"
|
||||||
|
inkscape:window-height="784"
|
||||||
|
id="namedview14"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.36"
|
||||||
|
inkscape:cx="50"
|
||||||
|
inkscape:cy="50"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="16"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="signature-verified-cutout" />
|
||||||
|
<!-- Generator: Sketch 3.0.4 (8053) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title
|
||||||
|
id="title4">signature-verified-cutout</title>
|
||||||
|
<desc
|
||||||
|
id="desc6">Created with Sketch.</desc>
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<g
|
||||||
|
id="Page-1"
|
||||||
|
sketch:type="MSPage"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke="none"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
fill="none">
|
||||||
|
<g
|
||||||
|
id="signature-verified-cutout"
|
||||||
|
sketch:type="MSArtboardGroup"
|
||||||
|
transform="translate(0.110156, 0.000000)"
|
||||||
|
fill="#000000">
|
||||||
|
<path
|
||||||
|
d="M 46.273291,77.5085 20,57.830916 27.91844,47.63497 43.309686,59.515226 70.31112,23 80.867825,30.778219 z"
|
||||||
|
sketch:type="MSShapeGroup"
|
||||||
|
id="path12"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -9,6 +9,7 @@ python copy OpenKeychain navigation black expand_more 24
|
|||||||
python copy OpenKeychain navigation white refresh 24
|
python copy OpenKeychain navigation white refresh 24
|
||||||
python copy OpenKeychain av white repeat 24
|
python copy OpenKeychain av white repeat 24
|
||||||
python copy OpenKeychain av grey repeat 24
|
python copy OpenKeychain av grey repeat 24
|
||||||
|
python copy OpenKeychain av black repeat 24
|
||||||
python copy OpenKeychain editor white mode_edit 24
|
python copy OpenKeychain editor white mode_edit 24
|
||||||
python copy OpenKeychain content white save 24
|
python copy OpenKeychain content white save 24
|
||||||
python copy OpenKeychain navigation black close 24
|
python copy OpenKeychain navigation black close 24
|
||||||
@@ -63,3 +64,10 @@ python copy OpenKeychain action white delete 24
|
|||||||
|
|
||||||
# yubikey dialog
|
# yubikey dialog
|
||||||
python copy OpenKeychain action black check_circle 48
|
python copy OpenKeychain action black check_circle 48
|
||||||
|
|
||||||
|
# preference header
|
||||||
|
python copy OpenKeychain file black cloud 24 # cloud
|
||||||
|
python copy OpenKeychain action black lock 24 # password settings
|
||||||
|
python copy OpenKeychain action black settings_ethernet 24 # proxy
|
||||||
|
python copy OpenKeychain notification black sync 24 # sync
|
||||||
|
python copy OpenKeychain action black extension 24 # experimental
|
||||||
|
|||||||
@@ -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" "yubi_icon" "ic_stat_notify"
|
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" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link"
|
||||||
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"
|
||||||
@@ -32,7 +32,7 @@ inkscape -w 72 -h 72 -e "$XXDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
|||||||
inkscape -w 96 -h 96 -e "$XXXDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
inkscape -w 96 -h 96 -e "$XXXDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"
|
||||||
done
|
done
|
||||||
|
|
||||||
for NAME in "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout"
|
for NAME in "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "status_signature_verified_inner"
|
||||||
do
|
do
|
||||||
echo $NAME
|
echo $NAME
|
||||||
inkscape -w 96 -h 96 -e "$MDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
|
inkscape -w 96 -h 96 -e "$MDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
|
||||||
@@ -41,7 +41,7 @@ inkscape -w 192 -h 192 -e "$XDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
|
|||||||
inkscape -w 256 -h 256 -e "$XXDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
|
inkscape -w 256 -h 256 -e "$XXDPI_DIR/${NAME}_96dp.png" "$SRC_DIR/$NAME.svg"
|
||||||
done
|
done
|
||||||
|
|
||||||
for NAME in "create_key_robot"
|
for NAME in "create_key_robot" "linked_dns" "linked_https" "linked_github" "linked_twitter" "account_key"
|
||||||
do
|
do
|
||||||
echo $NAME
|
echo $NAME
|
||||||
inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
|
inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" "$SRC_DIR/$NAME.svg"
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ dependencies {
|
|||||||
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||||
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 'com.splitwise:tokenautocomplete:1.3.3@aar'
|
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 'se.emilsjolander:stickylistheaders:2.6.0'
|
||||||
compile 'org.sufficientlysecure:html-textview:1.2'
|
compile 'org.sufficientlysecure:html-textview:1.2'
|
||||||
compile 'com.mikepenz:materialdrawer:3.0.9@aar'
|
compile 'com.mikepenz:materialdrawer:3.0.9@aar'
|
||||||
@@ -58,6 +59,7 @@ dependencies {
|
|||||||
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
||||||
compile 'org.apache.james:apache-mime4j-core:0.7.2'
|
compile 'org.apache.james:apache-mime4j-core:0.7.2'
|
||||||
compile 'org.apache.james:apache-mime4j-dom:0.7.2'
|
compile 'org.apache.james:apache-mime4j-dom:0.7.2'
|
||||||
|
compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0'
|
||||||
|
|
||||||
// libs as submodules
|
// libs as submodules
|
||||||
compile project(':extern:openpgp-api-lib:openpgp-api')
|
compile project(':extern:openpgp-api-lib:openpgp-api')
|
||||||
@@ -119,8 +121,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
versionCode 34100
|
versionCode 35100
|
||||||
versionName "3.4.1"
|
versionName "3.5.1"
|
||||||
applicationId "org.sufficientlysecure.keychain"
|
applicationId "org.sufficientlysecure.keychain"
|
||||||
// the androidjunitrunner is broken regarding coverage, see here:
|
// the androidjunitrunner is broken regarding coverage, see here:
|
||||||
// https://code.google.com/p/android/issues/detail?id=170607
|
// https://code.google.com/p/android/issues/detail?id=170607
|
||||||
@@ -143,9 +145,11 @@ android {
|
|||||||
|
|
||||||
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
||||||
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.account\""
|
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.account\""
|
||||||
|
buildConfigField "String", "PROVIDER_CONTENT_AUTHORITY", "\"org.sufficientlysecure.keychain.provider\""
|
||||||
|
|
||||||
// Reference them in .xml files.
|
// Reference them in .xml files.
|
||||||
resValue "string", "account_type", "org.sufficientlysecure.keychain.account"
|
resValue "string", "account_type", "org.sufficientlysecure.keychain.account"
|
||||||
|
resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.provider"
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
@@ -153,9 +157,15 @@ android {
|
|||||||
|
|
||||||
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
||||||
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.debug.account\""
|
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.debug.account\""
|
||||||
|
buildConfigField "String", "PROVIDER_CONTENT_AUTHORITY", "\"org.sufficientlysecure.keychain.debug.provider\""
|
||||||
|
|
||||||
|
// Github api for debug build only
|
||||||
|
buildConfigField "String", "GITHUB_CLIENT_ID", "\"7a011b66275f244d3f21\""
|
||||||
|
buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"eaced8a6655719d8c6848396de97b3f5d7a89fec\""
|
||||||
|
|
||||||
// Reference them in .xml files.
|
// Reference them in .xml files.
|
||||||
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
|
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
|
||||||
|
resValue "string", "provider_content_authority", "org.sufficientlysecure.keychain.debug.provider"
|
||||||
|
|
||||||
// Enable code coverage (Jacoco)
|
// Enable code coverage (Jacoco)
|
||||||
testCoverageEnabled true
|
testCoverageEnabled true
|
||||||
@@ -202,11 +212,11 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
|
incremental = true
|
||||||
// Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems
|
// Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems
|
||||||
preDexLibraries = false
|
preDexLibraries = false
|
||||||
// faster with incremental?
|
jumboMode = true
|
||||||
// incremental true
|
javaMaxHeapSize "2g"
|
||||||
javaMaxHeapSize "4g"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -71,7 +71,7 @@
|
|||||||
android:name=".KeychainApplication"
|
android:name=".KeychainApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.Keychain.Light">
|
android:theme="@style/Theme.Keychain.Light">
|
||||||
<activity
|
<activity
|
||||||
@@ -110,6 +110,13 @@
|
|||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:label="@string/title_edit_key" />
|
android:label="@string/title_edit_key" />
|
||||||
<!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape -->
|
<!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape -->
|
||||||
|
<activity
|
||||||
|
android:name=".ui.linked.LinkedIdWizard"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
|
android:label="@string/title_linked_create"
|
||||||
|
android:parentActivityName=".ui.ViewKeyActivity"
|
||||||
|
>
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.QrCodeViewActivity"
|
android:name=".ui.QrCodeViewActivity"
|
||||||
android:label="@string/share_qr_code_dialog_title" />
|
android:label="@string/share_qr_code_dialog_title" />
|
||||||
@@ -691,7 +698,6 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.OrbotRequiredDialogActivity"
|
android:name=".ui.OrbotRequiredDialogActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".ui.PassphraseWizardActivity" />
|
|
||||||
<!--
|
<!--
|
||||||
NOTE: singleTop is set to get NFC foreground dispatch to work.
|
NOTE: singleTop is set to get NFC foreground dispatch to work.
|
||||||
Then, all NFC intents will be broadcasted to onNewIntent() of this activity!
|
Then, all NFC intents will be broadcasted to onNewIntent() of this activity!
|
||||||
@@ -701,7 +707,7 @@
|
|||||||
-->
|
-->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.NfcOperationActivity"
|
android:name=".ui.NfcOperationActivity"
|
||||||
android:theme="@style/Theme.Keychain.Light.Dialog.SecurityToken"
|
android:theme="@style/Theme.Keychain.Light.Dialog"
|
||||||
android:allowTaskReparenting="true"
|
android:allowTaskReparenting="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=":Nfc" />
|
android:taskAffinity=":Nfc" />
|
||||||
@@ -723,10 +729,13 @@
|
|||||||
android:name=".service.KeychainService"
|
android:name=".service.KeychainService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!-- label is made to be "Keyserver Sync" since that is the only context in which
|
||||||
|
the user will see it-->
|
||||||
<provider
|
<provider
|
||||||
android:name=".provider.KeychainProvider"
|
android:name=".provider.KeychainProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:exported="false" />
|
android:exported="false"
|
||||||
|
android:label="@string/keyserver_sync_settings_title"/>
|
||||||
|
|
||||||
<!-- Internal classes of the remote APIs (not exported) -->
|
<!-- Internal classes of the remote APIs (not exported) -->
|
||||||
<activity
|
<activity
|
||||||
@@ -758,8 +767,9 @@
|
|||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- OpenPGP Remote API, this service has explicitly no permission requirements
|
<!-- DEPRECATED service,
|
||||||
because we are using our own package based allow/disallow system -->
|
using this service may lead to truncated data being returned to the caller
|
||||||
|
-->
|
||||||
<service
|
<service
|
||||||
android:name=".remote.OpenPgpService"
|
android:name=".remote.OpenPgpService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -771,6 +781,19 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<!-- OpenPGP Remote API, this service has explicitly no permission requirements
|
||||||
|
because we are using our own package based allow/disallow system -->
|
||||||
|
<service
|
||||||
|
android:name=".remote.OpenPgpService2"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:process=":remote_api_2"
|
||||||
|
tools:ignore="ExportedService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.openintents.openpgp.IOpenPgpService2" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<!-- Contact Sync services -->
|
<!-- Contact Sync services -->
|
||||||
<service
|
<service
|
||||||
android:name=".service.DummyAccountService"
|
android:name=".service.DummyAccountService"
|
||||||
@@ -800,6 +823,20 @@
|
|||||||
android:resource="@xml/custom_pgp_contacts_structure" />
|
android:resource="@xml/custom_pgp_contacts_structure" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.KeyserverSyncAdapterService"
|
||||||
|
android:exported="true"
|
||||||
|
android:process=":sync"
|
||||||
|
tools:ignore="ExportedService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.SyncAdapter" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.content.SyncAdapter"
|
||||||
|
android:resource="@xml/keyserver_sync_adapter_desc" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<!-- Storage Provider for temporary decrypted files -->
|
<!-- Storage Provider for temporary decrypted files -->
|
||||||
<provider
|
<provider
|
||||||
android:name=".provider.TemporaryStorageProvider"
|
android:name=".provider.TemporaryStorageProvider"
|
||||||
|
|||||||
12032
OpenKeychain/src/main/assets/word_confirm_list.txt
Normal file
@@ -19,14 +19,9 @@ package org.sufficientlysecure.keychain;
|
|||||||
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
|
||||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.BuildConfig;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
|
|
||||||
public final class Constants {
|
public final class Constants {
|
||||||
@@ -34,6 +29,7 @@ public final class Constants {
|
|||||||
public static final boolean DEBUG = BuildConfig.DEBUG;
|
public static final boolean DEBUG = BuildConfig.DEBUG;
|
||||||
public static final boolean DEBUG_LOG_DB_QUERIES = false;
|
public static final boolean DEBUG_LOG_DB_QUERIES = false;
|
||||||
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
|
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
|
||||||
|
public static final boolean DEBUG_KEYSERVER_SYNC = false;
|
||||||
|
|
||||||
public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
|
public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
|
||||||
|
|
||||||
@@ -43,7 +39,7 @@ public final class Constants {
|
|||||||
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
|
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
|
||||||
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
|
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
|
||||||
|
|
||||||
public static final String PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
|
public static final String PROVIDER_AUTHORITY = BuildConfig.PROVIDER_CONTENT_AUTHORITY;
|
||||||
public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
|
public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
|
||||||
|
|
||||||
public static final String CLIPBOARD_LABEL = "Keychain";
|
public static final String CLIPBOARD_LABEL = "Keychain";
|
||||||
@@ -81,6 +77,11 @@ public final class Constants {
|
|||||||
public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");
|
public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Notification {
|
||||||
|
public static final int PASSPHRASE_CACHE = 1;
|
||||||
|
public static final int KEYSERVER_SYNC_FAIL_ORBOT = 2;
|
||||||
|
}
|
||||||
|
|
||||||
public static final class Pref {
|
public static final class Pref {
|
||||||
public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl";
|
public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl";
|
||||||
public static final String PASSPHRASE_CACHE_SUBS = "passphraseCacheSubs";
|
public static final String PASSPHRASE_CACHE_SUBS = "passphraseCacheSubs";
|
||||||
@@ -104,12 +105,24 @@ public final class Constants {
|
|||||||
public static final String PROXY_PORT = "proxyPort";
|
public static final String PROXY_PORT = "proxyPort";
|
||||||
public static final String PROXY_TYPE = "proxyType";
|
public static final String PROXY_TYPE = "proxyType";
|
||||||
public static final String THEME = "theme";
|
public static final String THEME = "theme";
|
||||||
|
// keyserver sync settings
|
||||||
|
public static final String SYNC_CONTACTS = "syncContacts";
|
||||||
|
public static final String SYNC_KEYSERVER = "syncKeyserver";
|
||||||
|
// other settings
|
||||||
|
public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
|
||||||
|
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
|
||||||
|
public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase";
|
||||||
|
|
||||||
public static final class Theme {
|
public static final class Theme {
|
||||||
public static final String LIGHT = "light";
|
public static final String LIGHT = "light";
|
||||||
public static final String DARK = "dark";
|
public static final String DARK = "dark";
|
||||||
public static final String DEFAULT = Constants.Pref.Theme.LIGHT;
|
public static final String DEFAULT = Constants.Pref.Theme.LIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class ProxyType {
|
||||||
|
public static final String TYPE_HTTP = "proxyHttp";
|
||||||
|
public static final String TYPE_SOCKS = "proxySocks";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +136,7 @@ public final class Constants {
|
|||||||
|
|
||||||
public static final class Defaults {
|
public static final class Defaults {
|
||||||
public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu";
|
public static final String KEY_SERVERS = "hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu";
|
||||||
public static final int PREF_VERSION = 5;
|
public static final int PREF_VERSION = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class key {
|
public static final class key {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.app.Application;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
@@ -35,7 +34,7 @@ import android.widget.Toast;
|
|||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
|
||||||
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
|
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -97,7 +96,7 @@ public class KeychainApplication extends Application {
|
|||||||
setupAccountAsNeeded(this);
|
setupAccountAsNeeded(this);
|
||||||
|
|
||||||
// Update keyserver list as needed
|
// Update keyserver list as needed
|
||||||
Preferences.getPreferences(this).upgradePreferences();
|
Preferences.getPreferences(this).upgradePreferences(this);
|
||||||
|
|
||||||
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
|
TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
|
||||||
|
|
||||||
@@ -136,17 +135,20 @@ public class KeychainApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add OpenKeychain account to Android to link contacts with keys
|
* Add OpenKeychain account to Android to link contacts with keys and keyserver sync
|
||||||
*/
|
*/
|
||||||
public static void setupAccountAsNeeded(Context context) {
|
public static void setupAccountAsNeeded(Context context) {
|
||||||
try {
|
try {
|
||||||
AccountManager manager = AccountManager.get(context);
|
AccountManager manager = AccountManager.get(context);
|
||||||
Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
|
Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
|
||||||
if (accounts == null || accounts.length == 0) {
|
|
||||||
|
if (accounts.length == 0) {
|
||||||
Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
|
Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
|
||||||
if (manager.addAccountExplicitly(account, null, null)) {
|
if (manager.addAccountExplicitly(account, null, null)) {
|
||||||
|
// for contact sync
|
||||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||||
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
|
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
|
||||||
|
KeyserverSyncAdapterService.enableKeyserverSync(context);
|
||||||
} else {
|
} else {
|
||||||
Log.e(Constants.TAG, "Adding account failed!");
|
Log.e(Constants.TAG, "Adding account failed!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,9 +196,9 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
/**
|
/**
|
||||||
* returns a client with pinned certificate if necessary
|
* returns a client with pinned certificate if necessary
|
||||||
*
|
*
|
||||||
* @param url
|
* @param url url to be queried by client
|
||||||
* @param proxy
|
* @param proxy proxy to be used by client
|
||||||
* @return
|
* @return client with a pinned certificate if necesary
|
||||||
*/
|
*/
|
||||||
public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException {
|
public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException {
|
||||||
OkHttpClient client = new OkHttpClient();
|
OkHttpClient client = new OkHttpClient();
|
||||||
@@ -360,7 +360,7 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
try {
|
try {
|
||||||
data = query(request, proxy);
|
data = query(request, proxy);
|
||||||
} catch (HttpError httpError) {
|
} catch (HttpError httpError) {
|
||||||
Log.e(Constants.TAG, "Failed to get key at HkpKeyserver", httpError);
|
Log.d(Constants.TAG, "Failed to get key at HkpKeyserver", httpError);
|
||||||
throw new QueryFailedException("not found");
|
throw new QueryFailedException("not found");
|
||||||
}
|
}
|
||||||
Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data);
|
Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data);
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
|
||||||
|
public class LinkedAttribute extends UriAttribute {
|
||||||
|
|
||||||
|
public final LinkedResource mResource;
|
||||||
|
|
||||||
|
protected LinkedAttribute(URI uri, LinkedResource resource) {
|
||||||
|
super(uri);
|
||||||
|
if (resource == null) {
|
||||||
|
throw new AssertionError("resource must not be null in a LinkedIdentity!");
|
||||||
|
}
|
||||||
|
mResource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @DrawableRes int getDisplayIcon() {
|
||||||
|
return mResource.getDisplayIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayTitle(Context context) {
|
||||||
|
return mResource.getDisplayTitle(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayComment(Context context) {
|
||||||
|
return mResource.getDisplayComment(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
public abstract class LinkedResource {
|
||||||
|
|
||||||
|
public abstract URI toUri();
|
||||||
|
|
||||||
|
public abstract @DrawableRes int getDisplayIcon();
|
||||||
|
public abstract @StringRes int getVerifiedText(boolean isSecret);
|
||||||
|
public abstract String getDisplayTitle(Context context);
|
||||||
|
public abstract String getDisplayComment(Context context);
|
||||||
|
public boolean isViewable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public Intent getViewIntent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.apache.http.params.BasicHttpParams;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.DnsResource;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.thoughtcrime.ssl.pinning.util.PinningHelper;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class LinkedTokenResource extends LinkedResource {
|
||||||
|
|
||||||
|
protected final URI mSubUri;
|
||||||
|
protected final Set<String> mFlags;
|
||||||
|
protected final HashMap<String,String> mParams;
|
||||||
|
|
||||||
|
public static Pattern magicPattern =
|
||||||
|
Pattern.compile("\\[Verifying my (?:Open|)?PGP key(?::|) openpgp4fpr:([a-zA-Z0-9]+)]");
|
||||||
|
|
||||||
|
protected LinkedTokenResource(Set<String> flags, HashMap<String, String> params, URI uri) {
|
||||||
|
mFlags = flags;
|
||||||
|
mParams = params;
|
||||||
|
mSubUri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public URI getSubUri () {
|
||||||
|
return mSubUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getFlags () {
|
||||||
|
return new HashSet<>(mFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String,String> getParams () {
|
||||||
|
return new HashMap<>(mParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generate (byte[] fingerprint) {
|
||||||
|
return String.format("[Verifying my OpenPGP key: openpgp4fpr:%s]",
|
||||||
|
KeyFormattingUtils.convertFingerprintToHex(fingerprint));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static LinkedTokenResource fromUri (URI uri) {
|
||||||
|
|
||||||
|
if (!"openpgpid+token".equals(uri.getScheme())
|
||||||
|
&& !"openpgpid+cookie".equals(uri.getScheme())) {
|
||||||
|
Log.e(Constants.TAG, "unknown uri scheme in (suspected) linked id packet");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri.isOpaque()) {
|
||||||
|
Log.e(Constants.TAG, "non-opaque uri in (suspected) linked id packet");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String specific = uri.getSchemeSpecificPart();
|
||||||
|
if (!specific.contains("@")) {
|
||||||
|
Log.e(Constants.TAG, "unknown uri scheme in linked id packet");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] pieces = specific.split("@", 2);
|
||||||
|
URI subUri = URI.create(pieces[1]);
|
||||||
|
|
||||||
|
Set<String> flags = new HashSet<>();
|
||||||
|
HashMap<String,String> params = new HashMap<>();
|
||||||
|
if (!pieces[0].isEmpty()) {
|
||||||
|
String[] rawParams = pieces[0].split(";");
|
||||||
|
for (String param : rawParams) {
|
||||||
|
String[] p = param.split("=", 2);
|
||||||
|
if (p.length == 1) {
|
||||||
|
flags.add(param);
|
||||||
|
} else {
|
||||||
|
params.put(p[0], p[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return findResourceType(flags, params, subUri);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static LinkedTokenResource findResourceType (Set<String> flags,
|
||||||
|
HashMap<String,String> params, URI subUri) {
|
||||||
|
|
||||||
|
LinkedTokenResource res;
|
||||||
|
|
||||||
|
res = GenericHttpsResource.create(flags, params, subUri);
|
||||||
|
if (res != null) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// res = DnsResource.create(flags, params, subUri);
|
||||||
|
// if (res != null) {
|
||||||
|
// return res;
|
||||||
|
// }
|
||||||
|
res = TwitterResource.create(flags, params, subUri);
|
||||||
|
if (res != null) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
res = GithubResource.create(flags, params, subUri);
|
||||||
|
if (res != null) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI toUri () {
|
||||||
|
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
b.append("openpgpid+token:");
|
||||||
|
|
||||||
|
// add flags
|
||||||
|
if (mFlags != null) {
|
||||||
|
boolean first = true;
|
||||||
|
for (String flag : mFlags) {
|
||||||
|
if (!first) {
|
||||||
|
b.append(";");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
b.append(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add parameters
|
||||||
|
if (mParams != null) {
|
||||||
|
boolean first = true;
|
||||||
|
for (Entry<String, String> stringStringEntry : mParams.entrySet()) {
|
||||||
|
if (!first) {
|
||||||
|
b.append(";");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
b.append(stringStringEntry.getKey()).append("=").append(stringStringEntry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.append("@");
|
||||||
|
b.append(mSubUri);
|
||||||
|
|
||||||
|
return URI.create(b.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedVerifyResult verify(Context context, byte[] fingerprint) {
|
||||||
|
|
||||||
|
OperationLog log = new OperationLog();
|
||||||
|
log.add(LogType.MSG_LV, 0);
|
||||||
|
|
||||||
|
// Try to fetch resource. Logs for itself
|
||||||
|
String res = null;
|
||||||
|
try {
|
||||||
|
res = fetchResource(context, log, 1);
|
||||||
|
} catch (HttpStatusException e) {
|
||||||
|
// log verbose output to logcat
|
||||||
|
Log.e(Constants.TAG, "http error (" + e.getStatus() + "): " + e.getReason());
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR, 2, Integer.toString(e.getStatus()));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_URL, 2);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "io error", e);
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_IO, 2);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(Constants.TAG, "json error", e);
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == null) {
|
||||||
|
// if this is null, an error was recorded in fetchResource above
|
||||||
|
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "Resource data: '" + res + "'");
|
||||||
|
|
||||||
|
return verifyString(log, 1, res, fingerprint);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String fetchResource (Context context, OperationLog log, int indent)
|
||||||
|
throws HttpStatusException, IOException, JSONException;
|
||||||
|
|
||||||
|
protected Matcher matchResource (OperationLog log, int indent, String res) {
|
||||||
|
return magicPattern.matcher(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LinkedVerifyResult verifyString (OperationLog log, int indent,
|
||||||
|
String res,
|
||||||
|
byte[] fingerprint) {
|
||||||
|
|
||||||
|
log.add(LogType.MSG_LV_MATCH, indent);
|
||||||
|
Matcher match = matchResource(log, indent+1, res);
|
||||||
|
if (!match.find()) {
|
||||||
|
log.add(LogType.MSG_LV_MATCH_ERROR, 2);
|
||||||
|
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
String candidateFp = match.group(1).toLowerCase();
|
||||||
|
String fp = KeyFormattingUtils.convertFingerprintToHex(fingerprint);
|
||||||
|
if (!fp.equals(candidateFp)) {
|
||||||
|
log.add(LogType.MSG_LV_FP_ERROR, indent);
|
||||||
|
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
log.add(LogType.MSG_LV_FP_OK, indent);
|
||||||
|
|
||||||
|
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_OK, log);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // HttpRequestBase is deprecated
|
||||||
|
public static String getResponseBody(Context context, HttpRequestBase request)
|
||||||
|
throws IOException, HttpStatusException {
|
||||||
|
return getResponseBody(context, request, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // HttpRequestBase is deprecated
|
||||||
|
public static String getResponseBody(Context context, HttpRequestBase request, String[] pins)
|
||||||
|
throws IOException, HttpStatusException {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
request.setHeader("User-Agent", "Open Keychain");
|
||||||
|
|
||||||
|
|
||||||
|
HttpClient httpClient;
|
||||||
|
if (pins == null) {
|
||||||
|
httpClient = new DefaultHttpClient(new BasicHttpParams());
|
||||||
|
} else {
|
||||||
|
httpClient = PinningHelper.getPinnedHttpClient(context, pins);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
|
String reason = response.getStatusLine().getReasonPhrase();
|
||||||
|
|
||||||
|
if (statusCode != 200) {
|
||||||
|
throw new HttpStatusException(statusCode, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
InputStream inputStream = entity.getContent();
|
||||||
|
|
||||||
|
BufferedReader bReader = new BufferedReader(
|
||||||
|
new InputStreamReader(inputStream, "UTF-8"), 8);
|
||||||
|
String line;
|
||||||
|
while ((line = bReader.readLine()) != null) {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HttpStatusException extends Throwable {
|
||||||
|
|
||||||
|
private final int mStatusCode;
|
||||||
|
private final String mReason;
|
||||||
|
|
||||||
|
HttpStatusException(int statusCode, String reason) {
|
||||||
|
super("http status " + statusCode + ": " + reason);
|
||||||
|
mStatusCode = statusCode;
|
||||||
|
mReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return mStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return mReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked;
|
||||||
|
|
||||||
|
import org.spongycastle.util.Strings;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
|
||||||
|
/** The RawLinkedIdentity contains raw parsed data from a Linked Identity subpacket. */
|
||||||
|
public class UriAttribute {
|
||||||
|
|
||||||
|
public final URI mUri;
|
||||||
|
|
||||||
|
protected UriAttribute(URI uri) {
|
||||||
|
mUri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
return Strings.toUTF8ByteArray(mUri.toASCIIString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UriAttribute fromAttributeData(byte[] data) throws IOException {
|
||||||
|
WrappedUserAttribute att = WrappedUserAttribute.fromData(data);
|
||||||
|
|
||||||
|
byte[][] subpackets = att.getSubpackets();
|
||||||
|
if (subpackets.length >= 1) {
|
||||||
|
return fromSubpacketData(subpackets[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("no subpacket data");
|
||||||
|
}
|
||||||
|
|
||||||
|
static UriAttribute fromSubpacketData(byte[] data) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
String uriStr = Strings.fromUTF8ByteArray(data);
|
||||||
|
URI uri = URI.create(uriStr);
|
||||||
|
|
||||||
|
LinkedResource res = LinkedTokenResource.fromUri(uri);
|
||||||
|
if (res == null) {
|
||||||
|
return new UriAttribute(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LinkedAttribute(uri, res);
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.e(Constants.TAG, "error parsing uri in (suspected) linked id packet");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UriAttribute fromResource (LinkedTokenResource res) {
|
||||||
|
return new UriAttribute(res.toUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public WrappedUserAttribute toUserAttribute () {
|
||||||
|
return WrappedUserAttribute.fromSubpacket(WrappedUserAttribute.UAT_URI_ATTRIBUTE, getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public @DrawableRes int getDisplayIcon() {
|
||||||
|
return R.drawable.ic_warning_grey_24dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayTitle(Context context) {
|
||||||
|
return "Unknown Identity";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayComment(Context context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked.resources;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import de.measite.minidns.Client;
|
||||||
|
import de.measite.minidns.DNSMessage;
|
||||||
|
import de.measite.minidns.Question;
|
||||||
|
import de.measite.minidns.Record;
|
||||||
|
import de.measite.minidns.Record.CLASS;
|
||||||
|
import de.measite.minidns.Record.TYPE;
|
||||||
|
import de.measite.minidns.record.TXT;
|
||||||
|
|
||||||
|
public class DnsResource extends LinkedTokenResource {
|
||||||
|
|
||||||
|
final static Pattern magicPattern =
|
||||||
|
Pattern.compile("openpgpid\\+token=([a-zA-Z0-9]+)(?:#|;)([a-zA-Z0-9]+)");
|
||||||
|
|
||||||
|
String mFqdn;
|
||||||
|
CLASS mClass;
|
||||||
|
TYPE mType;
|
||||||
|
|
||||||
|
DnsResource(Set<String> flags, HashMap<String, String> params, URI uri,
|
||||||
|
String fqdn, CLASS clazz, TYPE type) {
|
||||||
|
super(flags, params, uri);
|
||||||
|
|
||||||
|
mFqdn = fqdn;
|
||||||
|
mClass = clazz;
|
||||||
|
mType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateText(byte[] fingerprint) {
|
||||||
|
|
||||||
|
return String.format("openpgp4fpr=%s",
|
||||||
|
KeyFormattingUtils.convertFingerprintToHex(fingerprint));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DnsResource createNew (String domain) {
|
||||||
|
HashSet<String> flags = new HashSet<>();
|
||||||
|
HashMap<String,String> params = new HashMap<>();
|
||||||
|
URI uri = URI.create("dns:" + domain + "?TYPE=TXT");
|
||||||
|
return create(flags, params, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DnsResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||||
|
if ( ! ("dns".equals(uri.getScheme())
|
||||||
|
&& (flags == null || flags.isEmpty())
|
||||||
|
&& (params == null || params.isEmpty()))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
String spec = uri.getSchemeSpecificPart();
|
||||||
|
// If there are // at the beginning, this includes an authority - we don't support those!
|
||||||
|
if (spec.startsWith("//")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] pieces = spec.split("\\?", 2);
|
||||||
|
// In either case, part before a ? is the fqdn
|
||||||
|
String fqdn = pieces[0];
|
||||||
|
// There may be a query part
|
||||||
|
/*
|
||||||
|
if (pieces.length > 1) {
|
||||||
|
// parse CLASS and TYPE query paramters
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CLASS clazz = CLASS.IN;
|
||||||
|
TYPE type = TYPE.TXT;
|
||||||
|
|
||||||
|
return new DnsResource(flags, params, uri, fqdn, clazz, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public String getFqdn() {
|
||||||
|
return mFqdn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String fetchResource (Context context, OperationLog log, int indent) {
|
||||||
|
|
||||||
|
Client c = new Client();
|
||||||
|
DNSMessage msg = c.query(new Question(mFqdn, mType, mClass));
|
||||||
|
Record aw = msg.getAnswers()[0];
|
||||||
|
TXT txt = (TXT) aw.getPayload();
|
||||||
|
return txt.getText().toLowerCase();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Matcher matchResource(OperationLog log, int indent, String res) {
|
||||||
|
return magicPattern.matcher(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @StringRes
|
||||||
|
int getVerifiedText(boolean isSecret) {
|
||||||
|
return isSecret ? R.string.linked_verified_secret_dns : R.string.linked_verified_dns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @DrawableRes int getDisplayIcon() {
|
||||||
|
return R.drawable.linked_dns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayTitle(Context context) {
|
||||||
|
return context.getString(R.string.linked_title_dns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayComment(Context context) {
|
||||||
|
return mFqdn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked.resources;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class GenericHttpsResource extends LinkedTokenResource {
|
||||||
|
|
||||||
|
GenericHttpsResource(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||||
|
super(flags, params, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateText (Context context, byte[] fingerprint) {
|
||||||
|
String token = LinkedTokenResource.generate(fingerprint);
|
||||||
|
|
||||||
|
return String.format(context.getResources().getString(R.string.linked_id_generic_text),
|
||||||
|
token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // HttpGet is deprecated
|
||||||
|
@Override
|
||||||
|
protected String fetchResource (Context context, OperationLog log, int indent)
|
||||||
|
throws HttpStatusException, IOException {
|
||||||
|
|
||||||
|
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
||||||
|
HttpGet httpGet = new HttpGet(mSubUri);
|
||||||
|
return getResponseBody(context, httpGet);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenericHttpsResource createNew (URI uri) {
|
||||||
|
HashSet<String> flags = new HashSet<>();
|
||||||
|
flags.add("generic");
|
||||||
|
HashMap<String,String> params = new HashMap<>();
|
||||||
|
return create(flags, params, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GenericHttpsResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||||
|
if ( ! ("https".equals(uri.getScheme())
|
||||||
|
&& flags != null && flags.size() == 1 && flags.contains("generic")
|
||||||
|
&& (params == null || params.isEmpty()))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new GenericHttpsResource(flags, params, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @DrawableRes
|
||||||
|
int getDisplayIcon() {
|
||||||
|
return R.drawable.linked_https;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @StringRes
|
||||||
|
int getVerifiedText(boolean isSecret) {
|
||||||
|
return isSecret ? R.string.linked_verified_secret_https : R.string.linked_verified_https;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayTitle(Context context) {
|
||||||
|
return context.getString(R.string.linked_title_https);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayComment(Context context) {
|
||||||
|
return mSubUri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isViewable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getViewIntent() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(mSubUri.toString()));
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked.resources;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
public class GithubResource extends LinkedTokenResource {
|
||||||
|
|
||||||
|
final String mHandle;
|
||||||
|
final String mGistId;
|
||||||
|
|
||||||
|
GithubResource(Set<String> flags, HashMap<String,String> params, URI uri,
|
||||||
|
String handle, String gistId) {
|
||||||
|
super(flags, params, uri);
|
||||||
|
|
||||||
|
mHandle = handle;
|
||||||
|
mGistId = gistId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generate(Context context, byte[] fingerprint) {
|
||||||
|
String token = LinkedTokenResource.generate(fingerprint);
|
||||||
|
|
||||||
|
return String.format(context.getResources().getString(R.string.linked_id_github_text), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // HttpGet is deprecated
|
||||||
|
@Override
|
||||||
|
protected String fetchResource (Context context, OperationLog log, int indent)
|
||||||
|
throws HttpStatusException, IOException, JSONException {
|
||||||
|
|
||||||
|
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
||||||
|
indent += 1;
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + mGistId);
|
||||||
|
String response = getResponseBody(context, httpGet);
|
||||||
|
|
||||||
|
JSONObject obj = new JSONObject(response);
|
||||||
|
|
||||||
|
JSONObject owner = obj.getJSONObject("owner");
|
||||||
|
if (!mHandle.equals(owner.getString("login"))) {
|
||||||
|
log.add(LogType.MSG_LV_ERROR_GITHUB_HANDLE, indent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject files = obj.getJSONObject("files");
|
||||||
|
Iterator<String> it = files.keys();
|
||||||
|
if (it.hasNext()) {
|
||||||
|
// TODO can there be multiple candidates?
|
||||||
|
JSONObject file = files.getJSONObject(it.next());
|
||||||
|
return file.getString("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.add(LogType.MSG_LV_ERROR_GITHUB_NOT_FOUND, indent);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // not used for now, but could be used to pick up earlier posted gist if already present?
|
||||||
|
@SuppressWarnings({ "deprecation", "unused" })
|
||||||
|
public static GithubResource searchInGithubStream(
|
||||||
|
Context context, String screenName, String needle, OperationLog log) {
|
||||||
|
|
||||||
|
// narrow the needle down to important part
|
||||||
|
Matcher matcher = magicPattern.matcher(needle);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
throw new AssertionError("Needle must contain token pattern! This is a programming error, please report.");
|
||||||
|
}
|
||||||
|
needle = matcher.group();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
JSONArray array; {
|
||||||
|
HttpGet httpGet =
|
||||||
|
new HttpGet("https://api.github.com/users/" + screenName + "/gists");
|
||||||
|
httpGet.setHeader("Content-Type", "application/json");
|
||||||
|
httpGet.setHeader("User-Agent", "OpenKeychain");
|
||||||
|
|
||||||
|
String response = getResponseBody(context, httpGet);
|
||||||
|
array = new JSONArray(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, j = Math.min(array.length(), 5); i < j; i++) {
|
||||||
|
JSONObject obj = array.getJSONObject(i);
|
||||||
|
|
||||||
|
JSONObject files = obj.getJSONObject("files");
|
||||||
|
Iterator<String> it = files.keys();
|
||||||
|
if (it.hasNext()) {
|
||||||
|
|
||||||
|
JSONObject file = files.getJSONObject(it.next());
|
||||||
|
String type = file.getString("type");
|
||||||
|
if (!"text/plain".equals(type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String id = obj.getString("id");
|
||||||
|
HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + id);
|
||||||
|
httpGet.setHeader("User-Agent", "OpenKeychain");
|
||||||
|
|
||||||
|
JSONObject gistObj = new JSONObject(getResponseBody(context, httpGet));
|
||||||
|
JSONObject gistFiles = gistObj.getJSONObject("files");
|
||||||
|
Iterator<String> gistIt = gistFiles.keys();
|
||||||
|
if (!gistIt.hasNext()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO can there be multiple candidates?
|
||||||
|
JSONObject gistFile = gistFiles.getJSONObject(gistIt.next());
|
||||||
|
String content = gistFile.getString("content");
|
||||||
|
if (!content.contains(needle)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
URI uri = URI.create("https://gist.github.com/" + screenName + "/" + id);
|
||||||
|
return create(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the results with the body of the response
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_NOTHING, 2);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} catch (HttpStatusException e) {
|
||||||
|
// log verbose output to logcat
|
||||||
|
Log.e(Constants.TAG, "http error (" + e.getStatus() + "): " + e.getReason());
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR, 2, Integer.toString(e.getStatus()));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_URL, 2);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "io error", e);
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_IO, 2);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(Constants.TAG, "json error", e);
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GithubResource create(URI uri) {
|
||||||
|
return create(new HashSet<String>(), new HashMap<String,String>(), uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GithubResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||||
|
|
||||||
|
// no params or flags
|
||||||
|
if (!flags.isEmpty() || !params.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern p = Pattern.compile("https://gist\\.github\\.com/([a-zA-Z0-9_]+)/([0-9a-f]+)");
|
||||||
|
Matcher match = p.matcher(uri.toString());
|
||||||
|
if (!match.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String handle = match.group(1);
|
||||||
|
String gistId = match.group(2);
|
||||||
|
|
||||||
|
return new GithubResource(flags, params, uri, handle, gistId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @DrawableRes
|
||||||
|
int getDisplayIcon() {
|
||||||
|
return R.drawable.linked_github;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @StringRes
|
||||||
|
int getVerifiedText(boolean isSecret) {
|
||||||
|
return isSecret ? R.string.linked_verified_secret_github : R.string.linked_verified_github;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayTitle(Context context) {
|
||||||
|
return context.getString(R.string.linked_title_github);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayComment(Context context) {
|
||||||
|
return mHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isViewable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getViewIntent() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(mSubUri.toString()));
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package org.sufficientlysecure.keychain.linked.resources;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.textuality.keybase.lib.JWalk;
|
||||||
|
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class TwitterResource extends LinkedTokenResource {
|
||||||
|
|
||||||
|
public static final String[] CERT_PINS = null; /*(new String[] {
|
||||||
|
// Symantec Class 3 Secure Server CA - G4
|
||||||
|
"513fb9743870b73440418d30930699ff"
|
||||||
|
};*/
|
||||||
|
|
||||||
|
final String mHandle;
|
||||||
|
final String mTweetId;
|
||||||
|
|
||||||
|
TwitterResource(Set<String> flags, HashMap<String,String> params,
|
||||||
|
URI uri, String handle, String tweetId) {
|
||||||
|
super(flags, params, uri);
|
||||||
|
|
||||||
|
mHandle = handle;
|
||||||
|
mTweetId = tweetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TwitterResource create(URI uri) {
|
||||||
|
return create(new HashSet<String>(), new HashMap<String,String>(), uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TwitterResource create(Set<String> flags, HashMap<String,String> params, URI uri) {
|
||||||
|
|
||||||
|
// no params or flags
|
||||||
|
if (!flags.isEmpty() || !params.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern p = Pattern.compile("https://twitter\\.com/([a-zA-Z0-9_]+)/status/([0-9]+)");
|
||||||
|
Matcher match = p.matcher(uri.toString());
|
||||||
|
if (!match.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String handle = match.group(1);
|
||||||
|
String tweetId = match.group(2);
|
||||||
|
|
||||||
|
return new TwitterResource(flags, params, uri, handle, tweetId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
protected String fetchResource(Context context, OperationLog log, int indent)
|
||||||
|
throws IOException, HttpStatusException, JSONException {
|
||||||
|
|
||||||
|
String authToken;
|
||||||
|
try {
|
||||||
|
authToken = getAuthToken(context);
|
||||||
|
} catch (IOException | HttpStatusException | JSONException e) {
|
||||||
|
log.add(LogType.MSG_LV_ERROR_TWITTER_AUTH, indent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpGet httpGet =
|
||||||
|
new HttpGet("https://api.twitter.com/1.1/statuses/show.json"
|
||||||
|
+ "?id=" + mTweetId
|
||||||
|
+ "&include_entities=false");
|
||||||
|
|
||||||
|
// construct a normal HTTPS request and include an Authorization
|
||||||
|
// header with the value of Bearer <>
|
||||||
|
httpGet.setHeader("Authorization", "Bearer " + authToken);
|
||||||
|
httpGet.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
String response = getResponseBody(context, httpGet, CERT_PINS);
|
||||||
|
JSONObject obj = new JSONObject(response);
|
||||||
|
JSONObject user = obj.getJSONObject("user");
|
||||||
|
if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) {
|
||||||
|
log.add(LogType.MSG_LV_ERROR_TWITTER_HANDLE, indent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the results with the body of the response
|
||||||
|
return obj.getString("text");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
log.add(LogType.MSG_LV_ERROR_TWITTER_RESPONSE, indent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @DrawableRes int getDisplayIcon() {
|
||||||
|
return R.drawable.linked_twitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @StringRes
|
||||||
|
int getVerifiedText(boolean isSecret) {
|
||||||
|
return isSecret ? R.string.linked_verified_secret_twitter : R.string.linked_verified_twitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayTitle(Context context) {
|
||||||
|
return context.getString(R.string.linked_title_twitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayComment(Context context) {
|
||||||
|
return "@" + mHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isViewable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getViewIntent() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(Uri.parse(mSubUri.toString()));
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static TwitterResource searchInTwitterStream(
|
||||||
|
Context context, String screenName, String needle, OperationLog log) {
|
||||||
|
|
||||||
|
String authToken;
|
||||||
|
try {
|
||||||
|
authToken = getAuthToken(context);
|
||||||
|
} catch (IOException | HttpStatusException | JSONException e) {
|
||||||
|
log.add(LogType.MSG_LV_ERROR_TWITTER_AUTH, 1);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpGet httpGet =
|
||||||
|
new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json"
|
||||||
|
+ "?screen_name=" + screenName
|
||||||
|
+ "&count=15"
|
||||||
|
+ "&include_rts=false"
|
||||||
|
+ "&trim_user=true"
|
||||||
|
+ "&exclude_replies=true");
|
||||||
|
|
||||||
|
// construct a normal HTTPS request and include an Authorization
|
||||||
|
// header with the value of Bearer <>
|
||||||
|
httpGet.setHeader("Authorization", "Bearer " + authToken);
|
||||||
|
httpGet.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
String response = getResponseBody(context, httpGet, CERT_PINS);
|
||||||
|
JSONArray array = new JSONArray(response);
|
||||||
|
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
JSONObject obj = array.getJSONObject(i);
|
||||||
|
String tweet = obj.getString("text");
|
||||||
|
if (tweet.contains(needle)) {
|
||||||
|
String id = obj.getString("id_str");
|
||||||
|
URI uri = URI.create("https://twitter.com/" + screenName + "/status/" + id);
|
||||||
|
return create(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the results with the body of the response
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_NOTHING, 1);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} catch (HttpStatusException e) {
|
||||||
|
// log verbose output to logcat
|
||||||
|
Log.e(Constants.TAG, "http error (" + e.getStatus() + "): " + e.getReason());
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR, 1, Integer.toString(e.getStatus()));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_URL, 1);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "io error", e);
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_IO, 1);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(Constants.TAG, "json error", e);
|
||||||
|
log.add(LogType.MSG_LV_FETCH_ERROR_FORMAT, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String cachedAuthToken;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static String getAuthToken(Context context)
|
||||||
|
throws IOException, HttpStatusException, JSONException {
|
||||||
|
if (cachedAuthToken != null) {
|
||||||
|
return cachedAuthToken;
|
||||||
|
}
|
||||||
|
String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c"
|
||||||
|
+ "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "==";
|
||||||
|
|
||||||
|
// Step 2: Obtain a bearer token
|
||||||
|
HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token");
|
||||||
|
httpPost.setHeader("Authorization", "Basic " + base64Encoded);
|
||||||
|
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
||||||
|
httpPost.setEntity(new StringEntity("grant_type=client_credentials"));
|
||||||
|
JSONObject rawAuthorization = new JSONObject(getResponseBody(context, httpPost, CERT_PINS));
|
||||||
|
|
||||||
|
// Applications should verify that the value associated with the
|
||||||
|
// token_type key of the returned object is bearer
|
||||||
|
if (!"bearer".equals(JWalk.getString(rawAuthorization, "token_type"))) {
|
||||||
|
throw new JSONException("Expected bearer token in response!");
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedAuthToken = rawAuthorization.getString("access_token");
|
||||||
|
return cachedAuthToken;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String rot13(String input) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < input.length(); i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (c >= 'a' && c <= 'm') c += 13;
|
||||||
|
else if (c >= 'A' && c <= 'M') c += 13;
|
||||||
|
else if (c >= 'n' && c <= 'z') c -= 13;
|
||||||
|
else if (c >= 'N' && c <= 'Z') c -= 13;
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -145,6 +145,8 @@ public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
|
|||||||
return new ExportResult(ExportResult.RESULT_ERROR, log);
|
return new ExportResult(ExportResult.RESULT_ERROR, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.add(LogType.MSG_EXPORT_FILE_NAME, 1, outputFile);
|
||||||
|
|
||||||
// check if storage is ready
|
// check if storage is ready
|
||||||
if (!FileHelper.isStorageMounted(outputFile)) {
|
if (!FileHelper.isStorageMounted(outputFile)) {
|
||||||
log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
|
log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ package org.sufficientlysecure.keychain.operations;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorCompletionService;
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
@@ -99,29 +98,9 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy);
|
return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries,
|
|
||||||
String keyServerUri, Proxy proxy) {
|
|
||||||
|
|
||||||
Iterator<ParcelableKeyRing> it = entries.iterator();
|
|
||||||
int numEntries = entries.size();
|
|
||||||
|
|
||||||
return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImportKeyResult serialKeyRingImport(List<ParcelableKeyRing> entries, String keyServerUri,
|
|
||||||
Progressable progressable, Proxy proxy) {
|
|
||||||
|
|
||||||
Iterator<ParcelableKeyRing> it = entries.iterator();
|
|
||||||
int numEntries = entries.size();
|
|
||||||
|
|
||||||
return serialKeyRingImport(it, numEntries, keyServerUri, progressable, proxy);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
|
private ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
|
||||||
String keyServerUri, Proxy proxy) {
|
String keyServerUri, Proxy proxy) {
|
||||||
|
|
||||||
// get entries from cached file
|
// get entries from cached file
|
||||||
try {
|
try {
|
||||||
@@ -143,7 +122,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Since the introduction of multithreaded import, we expect calling functions to handle the
|
* Since the introduction of multithreaded import, we expect calling functions to handle the
|
||||||
* key sync i,eContactSyncAdapterService.requestSync()
|
* contact-to-key sync i.e ContactSyncAdapterService.requestSync()
|
||||||
*
|
*
|
||||||
* @param entries keys to import
|
* @param entries keys to import
|
||||||
* @param num number of keys to import
|
* @param num number of keys to import
|
||||||
@@ -152,9 +131,9 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
* progress of a single key being imported
|
* progress of a single key being imported
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
|
private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
|
||||||
String keyServerUri, Progressable progressable,
|
String keyServerUri, Progressable progressable,
|
||||||
Proxy proxy) {
|
Proxy proxy) {
|
||||||
if (progressable != null) {
|
if (progressable != null) {
|
||||||
progressable.setProgress(R.string.progress_importing, 0, 100);
|
progressable.setProgress(R.string.progress_importing, 0, 100);
|
||||||
}
|
}
|
||||||
@@ -231,8 +210,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
|
log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
|
||||||
}
|
}
|
||||||
} catch (Keyserver.QueryFailedException e) {
|
} catch (Keyserver.QueryFailedException e) {
|
||||||
Log.e(Constants.TAG, "query failed", e);
|
Log.d(Constants.TAG, "query failed", e);
|
||||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
|
log.add(LogType.MSG_IMPORT_FETCH_ERROR_KEYSERVER, 3, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +243,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
} catch (Keyserver.QueryFailedException e) {
|
} catch (Keyserver.QueryFailedException e) {
|
||||||
// download failed, too bad. just proceed
|
// download failed, too bad. just proceed
|
||||||
Log.e(Constants.TAG, "query failed", e);
|
Log.e(Constants.TAG, "query failed", e);
|
||||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
|
log.add(LogType.MSG_IMPORT_FETCH_ERROR_KEYSERVER, 3, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,15 +254,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have an expected fingerprint, make sure it matches
|
// never import secret keys from keyserver!
|
||||||
if (entry.mExpectedFingerprint != null) {
|
if (entry.mBytes == null && key.isSecret()) {
|
||||||
if (!key.containsSubkey(entry.mExpectedFingerprint)) {
|
log.add(LogType.MSG_IMPORT_FETCH_ERROR_KEYSERVER_SECRET, 2);
|
||||||
log.add(LogType.MSG_IMPORT_FINGERPRINT_ERROR, 2);
|
badKeys += 1;
|
||||||
badKeys += 1;
|
continue;
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
log.add(LogType.MSG_IMPORT_FINGERPRINT_OK, 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another check if we have been cancelled
|
// Another check if we have been cancelled
|
||||||
@@ -293,31 +268,44 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SaveKeyringResult result;
|
SaveKeyringResult result;
|
||||||
mProviderHelper.clearLog();
|
// synchronizing prevents https://github.com/open-keychain/open-keychain/issues/1221
|
||||||
if (key.isSecret()) {
|
// and https://github.com/open-keychain/open-keychain/issues/1480
|
||||||
result = mProviderHelper.saveSecretKeyRing(key,
|
synchronized (mProviderHelper) {
|
||||||
new ProgressScaler(progressable, (int) (position * progSteps),
|
mProviderHelper.clearLog();
|
||||||
(int) ((position + 1) * progSteps), 100));
|
if (key.isSecret()) {
|
||||||
} else {
|
result = mProviderHelper.saveSecretKeyRing(key,
|
||||||
result = mProviderHelper.savePublicKeyRing(key,
|
new ProgressScaler(progressable, (int) (position * progSteps),
|
||||||
new ProgressScaler(progressable, (int) (position * progSteps),
|
(int) ((position + 1) * progSteps), 100));
|
||||||
(int) ((position + 1) * progSteps), 100));
|
} else {
|
||||||
|
result = mProviderHelper.savePublicKeyRing(key,
|
||||||
|
new ProgressScaler(progressable, (int) (position * progSteps),
|
||||||
|
(int) ((position + 1) * progSteps), 100), entry.mExpectedFingerprint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!result.success()) {
|
if (!result.success()) {
|
||||||
badKeys += 1;
|
badKeys += 1;
|
||||||
} else if (result.updated()) {
|
|
||||||
updatedKeys += 1;
|
|
||||||
importedMasterKeyIds.add(key.getMasterKeyId());
|
|
||||||
} else {
|
} else {
|
||||||
newKeys += 1;
|
if (result.updated()) {
|
||||||
if (key.isSecret()) {
|
updatedKeys += 1;
|
||||||
secret += 1;
|
importedMasterKeyIds.add(key.getMasterKeyId());
|
||||||
|
} else {
|
||||||
|
newKeys += 1;
|
||||||
|
if (key.isSecret()) {
|
||||||
|
secret += 1;
|
||||||
|
}
|
||||||
|
importedMasterKeyIds.add(key.getMasterKeyId());
|
||||||
|
}
|
||||||
|
if (entry.mBytes == null) {
|
||||||
|
// synonymous to isDownloadFromKeyserver.
|
||||||
|
// If no byte data was supplied, import from keyserver took place
|
||||||
|
// this prevents file imports being noted as keyserver imports
|
||||||
|
mProviderHelper.renewKeyLastUpdatedTime(key.getMasterKeyId(),
|
||||||
|
GregorianCalendar.getInstance().getTimeInMillis(),
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
importedMasterKeyIds.add(key.getMasterKeyId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.add(result, 2);
|
log.add(result, 2);
|
||||||
|
|
||||||
} catch (IOException | PgpGeneralException e) {
|
} catch (IOException | PgpGeneralException e) {
|
||||||
Log.e(Constants.TAG, "Encountered bad key on import!", e);
|
Log.e(Constants.TAG, "Encountered bad key on import!", e);
|
||||||
++badKeys;
|
++badKeys;
|
||||||
@@ -327,9 +315,15 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special: consolidate on secret key import (cannot be cancelled!)
|
// Special: consolidate on secret key import (cannot be cancelled!)
|
||||||
|
// synchronized on mProviderHelper to prevent
|
||||||
|
// https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes
|
||||||
|
// and re-inserts keys, which could conflict with a parallel db key update
|
||||||
if (secret > 0) {
|
if (secret > 0) {
|
||||||
setPreventCancel();
|
setPreventCancel();
|
||||||
ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(progressable);
|
ConsolidateResult result;
|
||||||
|
synchronized (mProviderHelper) {
|
||||||
|
result = mProviderHelper.consolidateDatabaseStep1(progressable);
|
||||||
|
}
|
||||||
log.add(result, 1);
|
log.add(result, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +380,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public OperationResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
|
public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
|
||||||
ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
|
ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
|
||||||
String keyServer = importInput.mKeyserver;
|
String keyServer = importInput.mKeyserver;
|
||||||
|
|
||||||
@@ -411,20 +405,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
} else {
|
} else {
|
||||||
proxy = cryptoInput.getParcelableProxy().getProxy();
|
proxy = cryptoInput.getParcelableProxy().getProxy();
|
||||||
}
|
}
|
||||||
// if there is more than one key with the same fingerprint, we do a serial import to
|
|
||||||
// prevent
|
result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy);
|
||||||
// https://github.com/open-keychain/open-keychain/issues/1221
|
|
||||||
HashSet<String> keyFingerprintSet = new HashSet<>();
|
|
||||||
for (int i = 0; i < keyList.size(); i++) {
|
|
||||||
keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint);
|
|
||||||
}
|
|
||||||
if (keyFingerprintSet.size() == keyList.size()) {
|
|
||||||
// all keys have unique fingerprints
|
|
||||||
result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer,
|
|
||||||
proxy);
|
|
||||||
} else {
|
|
||||||
result = serialKeyRingImport(keyList, keyServer, proxy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactSyncAdapterService.requestSync();
|
ContactSyncAdapterService.requestSync();
|
||||||
@@ -462,7 +444,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
||||||
list.add(pkRing);
|
list.add(pkRing);
|
||||||
|
|
||||||
return serialKeyRingImport(list, keyServer, ignoreProgressable, proxy);
|
return serialKeyRingImport(list.iterator(), 1, keyServer,
|
||||||
|
ignoreProgressable, proxy);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -486,18 +469,18 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
}
|
}
|
||||||
return accumulator.getConsolidatedResult();
|
return accumulator.getConsolidatedResult();
|
||||||
}
|
}
|
||||||
return null; // TODO: Decide if we should just crash instead of returning null
|
return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, new OperationLog());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to accumulate the results of individual key imports
|
* Used to accumulate the results of individual key imports
|
||||||
*/
|
*/
|
||||||
private class KeyImportAccumulator {
|
public static class KeyImportAccumulator {
|
||||||
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
||||||
Progressable mProgressable;
|
Progressable mProgressable;
|
||||||
private int mTotalKeys;
|
private int mTotalKeys;
|
||||||
private int mImportedKeys = 0;
|
private int mImportedKeys = 0;
|
||||||
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
|
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<>();
|
||||||
private int mBadKeys = 0;
|
private int mBadKeys = 0;
|
||||||
private int mNewKeys = 0;
|
private int mNewKeys = 0;
|
||||||
private int mUpdatedKeys = 0;
|
private int mUpdatedKeys = 0;
|
||||||
@@ -515,21 +498,17 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
|
public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
|
||||||
mTotalKeys = totalKeys;
|
mTotalKeys = totalKeys;
|
||||||
mProgressable = externalProgressable;
|
mProgressable = externalProgressable;
|
||||||
mProgressable.setProgress(0, totalKeys);
|
if (mProgressable != null) {
|
||||||
}
|
mProgressable.setProgress(0, totalKeys);
|
||||||
|
}
|
||||||
public int getTotalKeys() {
|
|
||||||
return mTotalKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getImportedKeys() {
|
|
||||||
return mImportedKeys;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void accumulateKeyImport(ImportKeyResult result) {
|
public synchronized void accumulateKeyImport(ImportKeyResult result) {
|
||||||
mImportedKeys++;
|
mImportedKeys++;
|
||||||
|
|
||||||
mProgressable.setProgress(mImportedKeys, mTotalKeys);
|
if (mProgressable != null) {
|
||||||
|
mProgressable.setProgress(mImportedKeys, mTotalKeys);
|
||||||
|
}
|
||||||
|
|
||||||
mImportLog.addAll(result.getLog().toList());//accumulates log
|
mImportLog.addAll(result.getLog().toList());//accumulates log
|
||||||
mBadKeys += result.mBadKeys;
|
mBadKeys += result.mBadKeys;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult;
|
import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
@@ -141,7 +141,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PgpDecryptVerify op = new PgpDecryptVerify(mContext, mProviderHelper, mProgressable);
|
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
|
||||||
|
|
||||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
|
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
|
||||||
.setSignedLiteralData(true)
|
.setSignedLiteralData(true)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
package org.sufficientlysecure.keychain.operations;
|
package org.sufficientlysecure.keychain.operations;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ 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.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
|
public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
|
||||||
|
|
||||||
public RevokeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
public RevokeOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||||
super(context, providerHelper, progressable);
|
super(context, providerHelper, progressable);
|
||||||
@@ -71,13 +70,15 @@ public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
|
|||||||
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
|
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveKeyringParcel saveKeyringParcel = getRevokedSaveKeyringParcel(masterKeyId,
|
SaveKeyringParcel saveKeyringParcel =
|
||||||
keyRing.getFingerprint());
|
new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
|
||||||
|
|
||||||
// all revoke operations are made atomic as of now
|
// all revoke operations are made atomic as of now
|
||||||
saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.mUpload, true,
|
saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.mUpload, true,
|
||||||
revokeKeyringParcel.mKeyserver);
|
revokeKeyringParcel.mKeyserver);
|
||||||
|
|
||||||
|
saveKeyringParcel.mRevokeSubKeys.add(masterKeyId);
|
||||||
|
|
||||||
InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext,
|
InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext,
|
||||||
mProviderHelper, mProgressable, mCancelled)
|
mProviderHelper, mProgressable, mCancelled)
|
||||||
.execute(saveKeyringParcel, cryptoInputParcel);
|
.execute(saveKeyringParcel, cryptoInputParcel);
|
||||||
@@ -92,54 +93,15 @@ public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
|
|||||||
log.add(OperationResult.LogType.MSG_REVOKE_OK, 1);
|
log.add(OperationResult.LogType.MSG_REVOKE_OK, 1);
|
||||||
return new RevokeResult(RevokeResult.RESULT_OK, log, masterKeyId);
|
return new RevokeResult(RevokeResult.RESULT_OK, log, masterKeyId);
|
||||||
} else {
|
} else {
|
||||||
log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1);
|
log.add(OperationResult.LogType.MSG_REVOKE_ERROR_KEY_FAIL, 1);
|
||||||
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
|
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
|
} catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
|
||||||
Log.e(Constants.TAG, "could not find key to revoke", e);
|
Log.e(Constants.TAG, "could not find key to revoke", e);
|
||||||
log.add(OperationResult.LogType.MSG_REVOKE_KEY_FAIL, 1);
|
log.add(OperationResult.LogType.MSG_REVOKE_ERROR_KEY_FAIL, 1);
|
||||||
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
|
return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SaveKeyringParcel getRevokedSaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
|
|
||||||
final String[] SUBKEYS_PROJECTION = new String[]{
|
|
||||||
KeychainContract.Keys.KEY_ID
|
|
||||||
};
|
|
||||||
final int INDEX_KEY_ID = 0;
|
|
||||||
|
|
||||||
Uri keysUri = KeychainContract.Keys.buildKeysUri(masterKeyId);
|
|
||||||
Cursor subKeyCursor =
|
|
||||||
mContext.getContentResolver().query(keysUri, SUBKEYS_PROJECTION, null, null, null);
|
|
||||||
|
|
||||||
SaveKeyringParcel saveKeyringParcel =
|
|
||||||
new SaveKeyringParcel(masterKeyId, fingerprint);
|
|
||||||
|
|
||||||
// add all subkeys, for revocation
|
|
||||||
while (subKeyCursor != null && subKeyCursor.moveToNext()) {
|
|
||||||
saveKeyringParcel.mRevokeSubKeys.add(subKeyCursor.getLong(INDEX_KEY_ID));
|
|
||||||
}
|
|
||||||
if (subKeyCursor != null) {
|
|
||||||
subKeyCursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String[] USER_IDS_PROJECTION = new String[]{
|
|
||||||
KeychainContract.UserPackets.USER_ID
|
|
||||||
};
|
|
||||||
final int INDEX_USER_ID = 0;
|
|
||||||
|
|
||||||
Uri userIdsUri = KeychainContract.UserPackets.buildUserIdsUri(masterKeyId);
|
|
||||||
Cursor userIdCursor = mContext.getContentResolver().query(
|
|
||||||
userIdsUri, USER_IDS_PROJECTION, null, null, null);
|
|
||||||
|
|
||||||
while (userIdCursor != null && userIdCursor.moveToNext()) {
|
|
||||||
saveKeyringParcel.mRevokeUserIds.add(userIdCursor.getString(INDEX_USER_ID));
|
|
||||||
}
|
|
||||||
if (userIdCursor != null) {
|
|
||||||
userIdCursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return saveKeyringParcel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2014 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 android.os.Parcel;
|
||||||
|
|
||||||
|
public class LinkedVerifyResult extends OperationResult {
|
||||||
|
|
||||||
|
public LinkedVerifyResult(int result, OperationLog log) {
|
||||||
|
super(result, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Construct from a parcel - trivial because we have no extra data. */
|
||||||
|
public LinkedVerifyResult(Parcel source) {
|
||||||
|
super(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Creator<LinkedVerifyResult> CREATOR = new Creator<LinkedVerifyResult>() {
|
||||||
|
public LinkedVerifyResult createFromParcel(final Parcel source) {
|
||||||
|
return new LinkedVerifyResult(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedVerifyResult[] newArray(final int size) {
|
||||||
|
return new LinkedVerifyResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.operations.results;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@@ -52,6 +53,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class OperationResult implements Parcelable {
|
public abstract class OperationResult implements Parcelable {
|
||||||
|
|
||||||
|
final static String INDENTATION_WHITESPACE = " ";
|
||||||
|
|
||||||
public static final String EXTRA_RESULT = "operation_result";
|
public static final String EXTRA_RESULT = "operation_result";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,6 +169,27 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
", mIndent=" + mIndent +
|
", mIndent=" + mIndent +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringBuilder getPrintableLogEntry(Resources resources, int indent) {
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int padding = mIndent +indent;
|
||||||
|
if (padding > INDENTATION_WHITESPACE.length()) {
|
||||||
|
padding = INDENTATION_WHITESPACE.length();
|
||||||
|
}
|
||||||
|
result.append(INDENTATION_WHITESPACE, 0, padding);
|
||||||
|
result.append(LOG_LEVEL_NAME[mType.mLevel.ordinal()]).append(' ');
|
||||||
|
|
||||||
|
// special case: first parameter may be a quantity
|
||||||
|
if (mParameters != null && mParameters.length > 0 && mParameters[0] instanceof Integer) {
|
||||||
|
result.append(resources.getQuantityString(mType.getMsgId(), (Integer) mParameters[0], mParameters));
|
||||||
|
} else {
|
||||||
|
result.append(resources.getString(mType.getMsgId(), mParameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SubLogEntryParcel extends LogEntryParcel {
|
public static class SubLogEntryParcel extends LogEntryParcel {
|
||||||
@@ -202,6 +226,17 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
dest.writeParcelable(mSubResult, 0);
|
dest.writeParcelable(mSubResult, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StringBuilder getPrintableLogEntry(Resources resources, int indent) {
|
||||||
|
|
||||||
|
LogEntryParcel subEntry = mSubResult.getLog().getLast();
|
||||||
|
if (subEntry != null) {
|
||||||
|
return subEntry.getPrintableLogEntry(resources, mIndent +indent);
|
||||||
|
} else {
|
||||||
|
return super.getPrintableLogEntry(resources, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Showable createNotify(final Activity activity) {
|
public Showable createNotify(final Activity activity) {
|
||||||
@@ -245,15 +280,15 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Notify.create(activity, logText, 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.snackbar_details);
|
}, R.string.snackbar_details);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,6 +324,8 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_IP_ERROR_IO_EXC (LogLevel.ERROR, R.string.msg_ip_error_io_exc),
|
MSG_IP_ERROR_IO_EXC (LogLevel.ERROR, R.string.msg_ip_error_io_exc),
|
||||||
MSG_IP_ERROR_OP_EXC (LogLevel.ERROR, R.string.msg_ip_error_op_exc),
|
MSG_IP_ERROR_OP_EXC (LogLevel.ERROR, R.string.msg_ip_error_op_exc),
|
||||||
MSG_IP_ERROR_REMOTE_EX (LogLevel.ERROR, R.string.msg_ip_error_remote_ex),
|
MSG_IP_ERROR_REMOTE_EX (LogLevel.ERROR, R.string.msg_ip_error_remote_ex),
|
||||||
|
MSG_IP_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_ip_fingerprint_error),
|
||||||
|
MSG_IP_FINGERPRINT_OK (LogLevel.INFO, R.string.msg_ip_fingerprint_ok),
|
||||||
MSG_IP_INSERT_KEYRING (LogLevel.DEBUG, R.string.msg_ip_insert_keyring),
|
MSG_IP_INSERT_KEYRING (LogLevel.DEBUG, R.string.msg_ip_insert_keyring),
|
||||||
MSG_IP_INSERT_SUBKEYS (LogLevel.DEBUG, R.string.msg_ip_insert_keys),
|
MSG_IP_INSERT_SUBKEYS (LogLevel.DEBUG, R.string.msg_ip_insert_keys),
|
||||||
MSG_IP_PREPARE (LogLevel.DEBUG, R.string.msg_ip_prepare),
|
MSG_IP_PREPARE (LogLevel.DEBUG, R.string.msg_ip_prepare),
|
||||||
@@ -631,6 +668,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_DC_TRAIL_SYM (LogLevel.DEBUG, R.string.msg_dc_trail_sym),
|
MSG_DC_TRAIL_SYM (LogLevel.DEBUG, R.string.msg_dc_trail_sym),
|
||||||
MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown),
|
MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown),
|
||||||
MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking),
|
MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking),
|
||||||
|
MSG_DC_INSECURE_ENCRYPTION_KEY (LogLevel.WARN, R.string.msg_dc_insecure_encryption_key),
|
||||||
MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO(LogLevel.WARN, R.string.msg_dc_insecure_symmetric_encryption_algo),
|
MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO(LogLevel.WARN, R.string.msg_dc_insecure_symmetric_encryption_algo),
|
||||||
MSG_DC_INSECURE_HASH_ALGO(LogLevel.ERROR, R.string.msg_dc_insecure_hash_algo),
|
MSG_DC_INSECURE_HASH_ALGO(LogLevel.ERROR, R.string.msg_dc_insecure_hash_algo),
|
||||||
MSG_DC_INSECURE_MDC_MISSING(LogLevel.ERROR, R.string.msg_dc_insecure_mdc_missing),
|
MSG_DC_INSECURE_MDC_MISSING(LogLevel.ERROR, R.string.msg_dc_insecure_mdc_missing),
|
||||||
@@ -704,21 +742,21 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
|
|
||||||
MSG_IMPORT_FETCH_ERROR (LogLevel.ERROR, R.string.msg_import_fetch_error),
|
MSG_IMPORT_FETCH_ERROR (LogLevel.ERROR, R.string.msg_import_fetch_error),
|
||||||
MSG_IMPORT_FETCH_ERROR_DECODE (LogLevel.ERROR, R.string.msg_import_fetch_error_decode),
|
MSG_IMPORT_FETCH_ERROR_DECODE (LogLevel.ERROR, R.string.msg_import_fetch_error_decode),
|
||||||
|
MSG_IMPORT_FETCH_ERROR_KEYSERVER(LogLevel.ERROR, R.string.msg_import_fetch_error_keyserver),
|
||||||
|
MSG_IMPORT_FETCH_ERROR_KEYSERVER_SECRET (LogLevel.ERROR, R.string.msg_import_fetch_error_keyserver_secret),
|
||||||
|
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
|
||||||
MSG_IMPORT_FETCH_KEYSERVER (LogLevel.INFO, R.string.msg_import_fetch_keyserver),
|
MSG_IMPORT_FETCH_KEYSERVER (LogLevel.INFO, R.string.msg_import_fetch_keyserver),
|
||||||
MSG_IMPORT_FETCH_KEYSERVER_OK (LogLevel.DEBUG, R.string.msg_import_fetch_keyserver_ok),
|
MSG_IMPORT_FETCH_KEYSERVER_OK (LogLevel.DEBUG, R.string.msg_import_fetch_keyserver_ok),
|
||||||
MSG_IMPORT_FETCH_KEYSERVER_ERROR (LogLevel.ERROR, R.string.msg_import_fetch_keyserver_error),
|
|
||||||
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
|
|
||||||
MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver),
|
MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver),
|
||||||
MSG_IMPORT_MERGE (LogLevel.DEBUG, R.string.msg_import_merge),
|
MSG_IMPORT_MERGE (LogLevel.DEBUG, R.string.msg_import_merge),
|
||||||
MSG_IMPORT_MERGE_ERROR (LogLevel.ERROR, R.string.msg_import_merge_error),
|
MSG_IMPORT_MERGE_ERROR (LogLevel.ERROR, R.string.msg_import_merge_error),
|
||||||
MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error),
|
|
||||||
MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok),
|
|
||||||
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
|
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
|
||||||
MSG_IMPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_import_error_io),
|
MSG_IMPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_import_error_io),
|
||||||
MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial),
|
MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial),
|
||||||
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
|
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
|
||||||
|
|
||||||
MSG_EXPORT (LogLevel.START, R.plurals.msg_export),
|
MSG_EXPORT (LogLevel.START, R.plurals.msg_export),
|
||||||
|
MSG_EXPORT_FILE_NAME (LogLevel.INFO, R.string.msg_export_file_name),
|
||||||
MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public),
|
MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public),
|
||||||
MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public),
|
MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public),
|
||||||
MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret),
|
MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret),
|
||||||
@@ -763,10 +801,9 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail),
|
MSG_DEL_FAIL (LogLevel.WARN, R.plurals.msg_del_fail),
|
||||||
|
|
||||||
MSG_REVOKE_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_revoke_error_empty),
|
MSG_REVOKE_ERROR_EMPTY (LogLevel.ERROR, R.string.msg_revoke_error_empty),
|
||||||
MSG_REVOKE_ERROR_MULTI_SECRET (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret),
|
MSG_REVOKE_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_revoke_error_not_found),
|
||||||
MSG_REVOKE_ERROR_NOT_FOUND (LogLevel.DEBUG, R.string.msg_revoke_error_multi_secret),
|
|
||||||
MSG_REVOKE (LogLevel.DEBUG, R.string.msg_revoke_key),
|
MSG_REVOKE (LogLevel.DEBUG, R.string.msg_revoke_key),
|
||||||
MSG_REVOKE_KEY_FAIL (LogLevel.ERROR, R.string.msg_revoke_key_fail),
|
MSG_REVOKE_ERROR_KEY_FAIL (LogLevel.ERROR, R.string.msg_revoke_key_fail),
|
||||||
MSG_REVOKE_OK (LogLevel.OK, R.string.msg_revoke_ok),
|
MSG_REVOKE_OK (LogLevel.OK, R.string.msg_revoke_ok),
|
||||||
|
|
||||||
// keybase verification
|
// keybase verification
|
||||||
@@ -781,17 +818,31 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR,
|
MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR,
|
||||||
R.string.msg_keybase_error_msg_payload_mismatch),
|
R.string.msg_keybase_error_msg_payload_mismatch),
|
||||||
|
|
||||||
// export log
|
// mime parsing
|
||||||
MSG_EXPORT_LOG(LogLevel.START,R.string.msg_export_log_start),
|
|
||||||
MSG_EXPORT_LOG_EXPORT_ERROR_NO_FILE(LogLevel.ERROR,R.string.msg_export_log_error_no_file),
|
|
||||||
MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN(LogLevel.ERROR,R.string.msg_export_log_error_fopen),
|
|
||||||
MSG_EXPORT_LOG_EXPORT_ERROR_WRITING(LogLevel.ERROR,R.string.msg_export_log_error_writing),
|
|
||||||
MSG_EXPORT_LOG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_log_success),
|
|
||||||
|
|
||||||
// mim parsing
|
|
||||||
MSG_MIME_PARSING(LogLevel.START,R.string.msg_mime_parsing_start),
|
MSG_MIME_PARSING(LogLevel.START,R.string.msg_mime_parsing_start),
|
||||||
MSG_MIME_PARSING_ERROR(LogLevel.ERROR,R.string.msg_mime_parsing_error),
|
MSG_MIME_PARSING_ERROR(LogLevel.ERROR,R.string.msg_mime_parsing_error),
|
||||||
MSG_MIME_PARSING_SUCCESS(LogLevel.OK,R.string.msg_mime_parsing_success),
|
MSG_MIME_PARSING_SUCCESS(LogLevel.OK,R.string.msg_mime_parsing_success),
|
||||||
|
|
||||||
|
MSG_LV (LogLevel.START, R.string.msg_lv),
|
||||||
|
MSG_LV_MATCH (LogLevel.DEBUG, R.string.msg_lv_match),
|
||||||
|
MSG_LV_MATCH_ERROR (LogLevel.ERROR, R.string.msg_lv_match_error),
|
||||||
|
MSG_LV_FP_OK (LogLevel.DEBUG, R.string.msg_lv_fp_ok),
|
||||||
|
MSG_LV_FP_ERROR (LogLevel.ERROR, R.string.msg_lv_fp_error),
|
||||||
|
|
||||||
|
MSG_LV_ERROR_TWITTER_AUTH (LogLevel.ERROR, R.string.msg_lv_error_twitter_auth),
|
||||||
|
MSG_LV_ERROR_TWITTER_HANDLE (LogLevel.ERROR, R.string.msg_lv_error_twitter_handle),
|
||||||
|
MSG_LV_ERROR_TWITTER_RESPONSE (LogLevel.ERROR, R.string.msg_lv_error_twitter_response),
|
||||||
|
MSG_LV_ERROR_GITHUB_HANDLE (LogLevel.ERROR, R.string.msg_lv_error_github_handle),
|
||||||
|
MSG_LV_ERROR_GITHUB_NOT_FOUND (LogLevel.ERROR, R.string.msg_lv_error_github_not_found),
|
||||||
|
|
||||||
|
MSG_LV_FETCH (LogLevel.DEBUG, R.string.msg_lv_fetch),
|
||||||
|
MSG_LV_FETCH_REDIR (LogLevel.DEBUG, R.string.msg_lv_fetch_redir),
|
||||||
|
MSG_LV_FETCH_OK (LogLevel.DEBUG, R.string.msg_lv_fetch_ok),
|
||||||
|
MSG_LV_FETCH_ERROR (LogLevel.ERROR, R.string.msg_lv_fetch_error),
|
||||||
|
MSG_LV_FETCH_ERROR_URL (LogLevel.ERROR, R.string.msg_lv_fetch_error_url),
|
||||||
|
MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io),
|
||||||
|
MSG_LV_FETCH_ERROR_FORMAT(LogLevel.ERROR, R.string.msg_lv_fetch_error_format),
|
||||||
|
MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing),
|
||||||
;
|
;
|
||||||
|
|
||||||
public final int mMsgId;
|
public final int mMsgId;
|
||||||
@@ -815,6 +866,10 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
OK, // should occur once at the end of a successful operation
|
OK, // should occur once at the end of a successful operation
|
||||||
CANCELLED, // should occur once at the end of a cancelled operation
|
CANCELLED, // should occur once at the end of a cancelled operation
|
||||||
}
|
}
|
||||||
|
// for print of debug log. keep those in sync with above!
|
||||||
|
static final String[] LOG_LEVEL_NAME = new String[] {
|
||||||
|
"[DEBUG]", "[INFO]", "[WARN]", "[ERROR]", "[START]", "[OK]", "[CANCEL]"
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
@@ -913,6 +968,20 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
public Iterator<LogEntryParcel> iterator() {
|
public Iterator<LogEntryParcel> iterator() {
|
||||||
return mParcels.iterator();
|
return mParcels.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns an indented String of an entire OperationLog
|
||||||
|
* @param indent padding to add at the start of all log entries, made for use with SubLogs
|
||||||
|
* @return printable, indented version of passed operationLog
|
||||||
|
*/
|
||||||
|
public String getPrintableOperationLog(Resources resources, int indent) {
|
||||||
|
StringBuilder log = new StringBuilder();
|
||||||
|
for (LogEntryParcel entry : this) {
|
||||||
|
log.append(entry.getPrintableLogEntry(resources, indent)).append("\n");
|
||||||
|
}
|
||||||
|
return log.toString().substring(0, log.length() -1); // get rid of extra new line
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
import org.spongycastle.openpgp.PGPKeyRing;
|
import org.spongycastle.openpgp.PGPKeyRing;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -28,6 +29,7 @@ import java.io.OutputStream;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
@@ -152,4 +154,14 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
|
|||||||
return getRing().getEncoded();
|
return getRing().getEncoded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean containsSubkey(String expectedFingerprint) {
|
||||||
|
for (CanonicalizedPublicKey key : publicKeyIterator()) {
|
||||||
|
if (KeyFormattingUtils.convertFingerprintToHex(
|
||||||
|
key.getFingerprint()).equalsIgnoreCase(expectedFingerprint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ import java.security.SignatureException;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> {
|
public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {
|
||||||
|
|
||||||
public PgpDecryptVerify(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||||
super(context, providerHelper, progressable);
|
super(context, providerHelper, progressable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +313,27 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class EncryptStreamResult {
|
||||||
|
|
||||||
|
// this is non-null iff an error occured, return directly
|
||||||
|
DecryptVerifyResult errorResult;
|
||||||
|
|
||||||
|
// for verification
|
||||||
|
PGPEncryptedData encryptedData;
|
||||||
|
InputStream cleartextStream;
|
||||||
|
|
||||||
|
int symmetricEncryptionAlgo = 0;
|
||||||
|
|
||||||
|
boolean skippedDisallowedKey = false;
|
||||||
|
boolean insecureEncryptionKey = false;
|
||||||
|
|
||||||
|
// convenience method to return with error
|
||||||
|
public EncryptStreamResult with(DecryptVerifyResult result) {
|
||||||
|
errorResult = result;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/** Decrypt and/or verify binary or ascii armored pgp data. */
|
/** Decrypt and/or verify binary or ascii armored pgp data. */
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -320,42 +341,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||||
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
|
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
|
||||||
|
|
||||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
|
||||||
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
|
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
|
|
||||||
log.add(LogType.MSG_DC, indent);
|
log.add(LogType.MSG_DC, indent);
|
||||||
indent += 1;
|
indent += 1;
|
||||||
|
|
||||||
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
|
|
||||||
PGPEncryptedDataList enc;
|
|
||||||
Object o = pgpF.nextObject();
|
|
||||||
|
|
||||||
int currentProgress = 0;
|
int currentProgress = 0;
|
||||||
updateProgress(R.string.progress_reading_data, currentProgress, 100);
|
updateProgress(R.string.progress_reading_data, currentProgress, 100);
|
||||||
|
|
||||||
if (o instanceof PGPEncryptedDataList) {
|
|
||||||
enc = (PGPEncryptedDataList) o;
|
|
||||||
} else {
|
|
||||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enc == null) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream clear;
|
|
||||||
PGPEncryptedData encryptedData;
|
|
||||||
|
|
||||||
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
|
||||||
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
|
||||||
CanonicalizedSecretKey secretEncryptionKey = null;
|
|
||||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
|
||||||
boolean asymmetricPacketFound = false;
|
|
||||||
boolean symmetricPacketFound = false;
|
|
||||||
boolean anyPacketFound = false;
|
|
||||||
|
|
||||||
// If the input stream is armored, and there is a charset specified, take a note for later
|
// If the input stream is armored, and there is a charset specified, take a note for later
|
||||||
// https://tools.ietf.org/html/rfc4880#page56
|
// https://tools.ietf.org/html/rfc4880#page56
|
||||||
String charset = null;
|
String charset = null;
|
||||||
@@ -375,261 +368,53 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Passphrase passphrase = null;
|
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||||
boolean skippedDisallowedKey = false;
|
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
|
||||||
|
|
||||||
// go through all objects and find one we can decrypt
|
JcaPGPObjectFactory plainFact;
|
||||||
while (it.hasNext()) {
|
Object dataChunk;
|
||||||
Object obj = it.next();
|
EncryptStreamResult esResult = null;
|
||||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
{ // resolve encrypted (symmetric and asymmetric) packets
|
||||||
anyPacketFound = true;
|
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
|
||||||
|
Object obj = pgpF.nextObject();
|
||||||
|
|
||||||
currentProgress += 2;
|
if (obj instanceof PGPEncryptedDataList) {
|
||||||
updateProgress(R.string.progress_finding_key, currentProgress, 100);
|
esResult = handleEncryptedPacket(
|
||||||
|
input, cryptoInput, (PGPEncryptedDataList) obj, log, indent, currentProgress);
|
||||||
|
|
||||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
// if there is an error, nothing left to do here
|
||||||
long subKeyId = encData.getKeyID();
|
if (esResult.errorResult != null) {
|
||||||
|
return esResult.errorResult;
|
||||||
log.add(LogType.MSG_DC_ASYM, indent,
|
|
||||||
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
|
||||||
|
|
||||||
CanonicalizedSecretKeyRing secretKeyRing;
|
|
||||||
try {
|
|
||||||
// get actual keyring object based on master key id
|
|
||||||
secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(
|
|
||||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)
|
|
||||||
);
|
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
|
||||||
// continue with the next packet in the while loop
|
|
||||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (secretKeyRing == null) {
|
|
||||||
// continue with the next packet in the while loop
|
|
||||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow only specific keys for decryption?
|
// if this worked out so far, the data is encrypted
|
||||||
if (input.getAllowedKeyIds() != null) {
|
decryptionResultBuilder.setEncrypted(true);
|
||||||
long masterKeyId = secretKeyRing.getMasterKeyId();
|
|
||||||
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
|
|
||||||
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
|
|
||||||
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
|
||||||
|
|
||||||
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
|
if (esResult.insecureEncryptionKey) {
|
||||||
// this key is in our db, but NOT allowed!
|
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||||
// continue with the next packet in the while loop
|
|
||||||
skippedDisallowedKey = true;
|
|
||||||
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get subkey which has been used for this encryption packet
|
|
||||||
secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
|
|
||||||
if (secretEncryptionKey == null) {
|
|
||||||
// should actually never happen, so no need to be more specific.
|
|
||||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* secret key exists in database and is allowed! */
|
|
||||||
asymmetricPacketFound = true;
|
|
||||||
|
|
||||||
encryptedDataAsymmetric = encData;
|
|
||||||
|
|
||||||
if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
|
|
||||||
passphrase = null;
|
|
||||||
} else if (cryptoInput.hasPassphrase()) {
|
|
||||||
passphrase = cryptoInput.getPassphrase();
|
|
||||||
} else {
|
|
||||||
// if no passphrase was explicitly set try to get it from the cache service
|
|
||||||
try {
|
|
||||||
// returns "" if key has no passphrase
|
|
||||||
passphrase = getCachedPassphrase(subKeyId);
|
|
||||||
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
|
||||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
|
||||||
if (passphrase == null) {
|
|
||||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
|
||||||
return new DecryptVerifyResult(log,
|
|
||||||
RequiredInputParcel.createRequiredDecryptPassphrase(
|
|
||||||
secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
|
|
||||||
cryptoInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for insecure encryption key
|
|
||||||
if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) {
|
|
||||||
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
|
||||||
decryptionResultBuilder.setInsecure(true);
|
decryptionResultBuilder.setInsecure(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// break out of while, only decrypt the first packet where we have a key
|
// Check for insecure encryption algorithms!
|
||||||
break;
|
if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(esResult.symmetricEncryptionAlgo)) {
|
||||||
|
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||||
} else if (obj instanceof PGPPBEEncryptedData) {
|
decryptionResultBuilder.setInsecure(true);
|
||||||
anyPacketFound = true;
|
|
||||||
|
|
||||||
log.add(LogType.MSG_DC_SYM, indent);
|
|
||||||
|
|
||||||
if (!input.isAllowSymmetricDecryption()) {
|
|
||||||
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
plainFact = new JcaPGPObjectFactory(esResult.cleartextStream);
|
||||||
* When mAllowSymmetricDecryption == true and we find a data packet here,
|
dataChunk = plainFact.nextObject();
|
||||||
* we do not search for other available asymmetric packets!
|
|
||||||
*/
|
|
||||||
symmetricPacketFound = true;
|
|
||||||
|
|
||||||
encryptedDataSymmetric = (PGPPBEEncryptedData) obj;
|
|
||||||
|
|
||||||
// if no passphrase is given, return here
|
|
||||||
// indicating that a passphrase is missing!
|
|
||||||
if (!cryptoInput.hasPassphrase()) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
passphrase = getCachedPassphrase(key.symmetric);
|
|
||||||
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
|
||||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
|
||||||
// nvm
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passphrase == null) {
|
|
||||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
|
||||||
return new DecryptVerifyResult(log,
|
|
||||||
RequiredInputParcel.createRequiredSymmetricPassphrase(),
|
|
||||||
cryptoInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
passphrase = cryptoInput.getPassphrase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// break out of while, only decrypt the first packet
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// More data, just acknowledge and ignore.
|
|
||||||
while (it.hasNext()) {
|
|
||||||
Object obj = it.next();
|
|
||||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
|
||||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
|
||||||
long subKeyId = encData.getKeyID();
|
|
||||||
log.add(LogType.MSG_DC_TRAIL_ASYM, indent,
|
|
||||||
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
|
||||||
} else if (obj instanceof PGPPBEEncryptedData) {
|
|
||||||
log.add(LogType.MSG_DC_TRAIL_SYM, indent);
|
|
||||||
} else {
|
} else {
|
||||||
log.add(LogType.MSG_DC_TRAIL_UNKNOWN, indent);
|
decryptionResultBuilder.setEncrypted(false);
|
||||||
|
|
||||||
|
plainFact = pgpF;
|
||||||
|
dataChunk = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.add(LogType.MSG_DC_PREP_STREAMS, indent);
|
log.add(LogType.MSG_DC_PREP_STREAMS, indent);
|
||||||
|
|
||||||
// we made sure above one of these two would be true
|
|
||||||
int symmetricEncryptionAlgo;
|
|
||||||
if (symmetricPacketFound) {
|
|
||||||
currentProgress += 2;
|
|
||||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
|
||||||
|
|
||||||
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
|
|
||||||
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
|
|
||||||
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
|
||||||
passphrase.getCharArray());
|
|
||||||
|
|
||||||
try {
|
|
||||||
clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
|
||||||
} catch (PGPDataValidationException e) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent +1);
|
|
||||||
return new DecryptVerifyResult(log,
|
|
||||||
RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedData = encryptedDataSymmetric;
|
|
||||||
|
|
||||||
symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory);
|
|
||||||
} else if (asymmetricPacketFound) {
|
|
||||||
currentProgress += 2;
|
|
||||||
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
|
||||||
if (!secretEncryptionKey.unlock(passphrase)) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
} catch (PgpGeneralException e) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentProgress += 2;
|
|
||||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
|
||||||
|
|
||||||
CachingDataDecryptorFactory decryptorFactory
|
|
||||||
= secretEncryptionKey.getCachingDecryptorFactory(cryptoInput);
|
|
||||||
|
|
||||||
// special case: if the decryptor does not have a session key cached for this encrypted
|
|
||||||
// data, and can't actually decrypt on its own, return a pending intent
|
|
||||||
if (!decryptorFactory.canDecrypt()
|
|
||||||
&& !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
|
|
||||||
|
|
||||||
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
|
||||||
return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
|
|
||||||
secretEncryptionKey.getRing().getMasterKeyId(),
|
|
||||||
secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
|
|
||||||
),
|
|
||||||
cryptoInput);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
|
||||||
} catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
|
||||||
|
|
||||||
cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys());
|
|
||||||
|
|
||||||
encryptedData = encryptedDataAsymmetric;
|
|
||||||
} else {
|
|
||||||
// there wasn't even any useful data
|
|
||||||
if (!anyPacketFound) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_NO_DATA, log);
|
|
||||||
}
|
|
||||||
// there was data but key wasn't allowed
|
|
||||||
if (skippedDisallowedKey) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_KEY_DISALLOWED, log);
|
|
||||||
}
|
|
||||||
// no packet has been found where we have the corresponding secret key in our db
|
|
||||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
decryptionResultBuilder.setEncrypted(true);
|
|
||||||
|
|
||||||
// Check for insecure encryption algorithms!
|
|
||||||
if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(symmetricEncryptionAlgo)) {
|
|
||||||
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
|
||||||
decryptionResultBuilder.setInsecure(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
|
|
||||||
Object dataChunk = plainFact.nextObject();
|
|
||||||
int signatureIndex = -1;
|
int signatureIndex = -1;
|
||||||
CanonicalizedPublicKeyRing signingRing = null;
|
CanonicalizedPublicKeyRing signingRing = null;
|
||||||
CanonicalizedPublicKey signingKey = null;
|
CanonicalizedPublicKey signingKey = null;
|
||||||
@@ -637,6 +422,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
log.add(LogType.MSG_DC_CLEAR, indent);
|
log.add(LogType.MSG_DC_CLEAR, indent);
|
||||||
indent += 1;
|
indent += 1;
|
||||||
|
|
||||||
|
// resolve compressed data
|
||||||
if (dataChunk instanceof PGPCompressedData) {
|
if (dataChunk instanceof PGPCompressedData) {
|
||||||
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
|
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
|
||||||
currentProgress += 2;
|
currentProgress += 2;
|
||||||
@@ -649,6 +435,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
plainFact = fact;
|
plainFact = fact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolve leading signature data
|
||||||
PGPOnePassSignature signature = null;
|
PGPOnePassSignature signature = null;
|
||||||
if (dataChunk instanceof PGPOnePassSignatureList) {
|
if (dataChunk instanceof PGPOnePassSignatureList) {
|
||||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1);
|
log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1);
|
||||||
@@ -710,159 +497,158 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
|
|
||||||
OpenPgpMetadata metadata;
|
OpenPgpMetadata metadata;
|
||||||
|
|
||||||
if (dataChunk instanceof PGPLiteralData) {
|
if ( ! (dataChunk instanceof PGPLiteralData)) {
|
||||||
log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
|
|
||||||
indent += 2;
|
|
||||||
currentProgress += 4;
|
|
||||||
updateProgress(R.string.progress_decrypting, currentProgress, 100);
|
|
||||||
|
|
||||||
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
|
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent);
|
||||||
|
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||||
|
|
||||||
String originalFilename = literalData.getFileName();
|
}
|
||||||
String mimeType = null;
|
|
||||||
if (literalData.getFormat() == PGPLiteralData.TEXT
|
log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
|
||||||
|| literalData.getFormat() == PGPLiteralData.UTF8) {
|
indent += 2;
|
||||||
mimeType = "text/plain";
|
currentProgress += 4;
|
||||||
|
updateProgress(R.string.progress_decrypting, currentProgress, 100);
|
||||||
|
|
||||||
|
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
|
||||||
|
|
||||||
|
String originalFilename = literalData.getFileName();
|
||||||
|
if (originalFilename.contains("/")) {
|
||||||
|
originalFilename = originalFilename.substring(originalFilename.lastIndexOf('/'));
|
||||||
|
}
|
||||||
|
String mimeType = null;
|
||||||
|
if (literalData.getFormat() == PGPLiteralData.TEXT
|
||||||
|
|| literalData.getFormat() == PGPLiteralData.UTF8) {
|
||||||
|
mimeType = "text/plain";
|
||||||
|
} else {
|
||||||
|
// try to guess from file ending
|
||||||
|
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
|
||||||
|
if (extension != null) {
|
||||||
|
MimeTypeMap mime = MimeTypeMap.getSingleton();
|
||||||
|
mimeType = mime.getMimeTypeFromExtension(extension);
|
||||||
|
}
|
||||||
|
if (mimeType == null) {
|
||||||
|
mimeType = "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"".equals(originalFilename)) {
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
|
||||||
|
}
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1,
|
||||||
|
mimeType);
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
|
||||||
|
new Date(literalData.getModificationTime().getTime()).toString());
|
||||||
|
|
||||||
|
// return here if we want to decrypt the metadata only
|
||||||
|
if (input.isDecryptMetadataOnly()) {
|
||||||
|
|
||||||
|
// this operation skips the entire stream to find the data length!
|
||||||
|
Long originalSize = literalData.findDataLength();
|
||||||
|
|
||||||
|
if (originalSize != null) {
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
|
||||||
|
Long.toString(originalSize));
|
||||||
} else {
|
} else {
|
||||||
// try to guess from file ending
|
log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
|
||||||
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
|
|
||||||
if (extension != null) {
|
|
||||||
MimeTypeMap mime = MimeTypeMap.getSingleton();
|
|
||||||
mimeType = mime.getMimeTypeFromExtension(extension);
|
|
||||||
}
|
|
||||||
if (mimeType == null) {
|
|
||||||
mimeType = "application/octet-stream";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!"".equals(originalFilename)) {
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
|
|
||||||
}
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1,
|
|
||||||
mimeType);
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
|
|
||||||
new Date(literalData.getModificationTime().getTime()).toString());
|
|
||||||
|
|
||||||
// return here if we want to decrypt the metadata only
|
|
||||||
if (input.isDecryptMetadataOnly()) {
|
|
||||||
|
|
||||||
// this operation skips the entire stream to find the data length!
|
|
||||||
Long originalSize = literalData.findDataLength();
|
|
||||||
|
|
||||||
if (originalSize != null) {
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
|
|
||||||
Long.toString(originalSize));
|
|
||||||
} else {
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata = new OpenPgpMetadata(
|
|
||||||
originalFilename,
|
|
||||||
mimeType,
|
|
||||||
literalData.getModificationTime().getTime(),
|
|
||||||
originalSize == null ? 0 : originalSize);
|
|
||||||
|
|
||||||
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
|
||||||
DecryptVerifyResult result =
|
|
||||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
|
||||||
result.setCharset(charset);
|
|
||||||
result.setDecryptionMetadata(metadata);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int endProgress;
|
|
||||||
if (signature != null) {
|
|
||||||
endProgress = 90;
|
|
||||||
} else if (encryptedData.isIntegrityProtected()) {
|
|
||||||
endProgress = 95;
|
|
||||||
} else {
|
|
||||||
endProgress = 100;
|
|
||||||
}
|
|
||||||
ProgressScaler progressScaler =
|
|
||||||
new ProgressScaler(mProgressable, currentProgress, endProgress, 100);
|
|
||||||
|
|
||||||
InputStream dataIn = literalData.getInputStream();
|
|
||||||
|
|
||||||
long alreadyWritten = 0;
|
|
||||||
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
|
|
||||||
int length;
|
|
||||||
byte[] buffer = new byte[1 << 16];
|
|
||||||
while ((length = dataIn.read(buffer)) > 0) {
|
|
||||||
// Log.d(Constants.TAG, "read bytes: " + length);
|
|
||||||
if (out != null) {
|
|
||||||
out.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update signature buffer if signature is also present
|
|
||||||
if (signature != null) {
|
|
||||||
signature.update(buffer, 0, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
alreadyWritten += length;
|
|
||||||
if (wholeSize > 0) {
|
|
||||||
long progress = 100 * alreadyWritten / wholeSize;
|
|
||||||
// stop at 100% for wrong file sizes...
|
|
||||||
if (progress > 100) {
|
|
||||||
progress = 100;
|
|
||||||
}
|
|
||||||
progressScaler.setProgress((int) progress, 100);
|
|
||||||
}
|
|
||||||
// TODO: slow annealing to fake a progress?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata = new OpenPgpMetadata(
|
metadata = new OpenPgpMetadata(
|
||||||
originalFilename,
|
originalFilename,
|
||||||
mimeType,
|
mimeType,
|
||||||
literalData.getModificationTime().getTime(),
|
literalData.getModificationTime().getTime(),
|
||||||
alreadyWritten);
|
originalSize == null ? 0 : originalSize);
|
||||||
|
|
||||||
if (signature != null) {
|
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
||||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
DecryptVerifyResult result =
|
||||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||||
|
result.setCharset(charset);
|
||||||
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
|
result.setDecryptionMetadata(metadata);
|
||||||
PGPSignature messageSignature = signatureList.get(signatureIndex);
|
return result;
|
||||||
|
|
||||||
// TODO: what about binary signatures?
|
|
||||||
|
|
||||||
// Verify signature
|
|
||||||
boolean validSignature = signature.verify(messageSignature);
|
|
||||||
if (validSignature) {
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
|
||||||
} else {
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for insecure hash algorithms
|
|
||||||
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
|
||||||
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
|
||||||
signatureResultBuilder.setInsecure(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
signatureResultBuilder.setValidSignature(validSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
indent -= 1;
|
|
||||||
} else {
|
|
||||||
// If there is no literalData, we don't have any metadata
|
|
||||||
metadata = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encryptedData.isIntegrityProtected()) {
|
int endProgress;
|
||||||
updateProgress(R.string.progress_verifying_integrity, 95, 100);
|
if (signature != null) {
|
||||||
|
endProgress = 90;
|
||||||
if (encryptedData.verify()) {
|
} else if (esResult != null && esResult.encryptedData.isIntegrityProtected()) {
|
||||||
log.add(LogType.MSG_DC_INTEGRITY_CHECK_OK, indent);
|
endProgress = 95;
|
||||||
} else {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent);
|
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// If no valid signature is present:
|
endProgress = 100;
|
||||||
// Handle missing integrity protection like failed integrity protection!
|
}
|
||||||
// The MDC packet can be stripped by an attacker!
|
ProgressScaler progressScaler =
|
||||||
Log.d(Constants.TAG, "MDC fail");
|
new ProgressScaler(mProgressable, currentProgress, endProgress, 100);
|
||||||
if (!signatureResultBuilder.isValidSignature()) {
|
|
||||||
|
InputStream dataIn = literalData.getInputStream();
|
||||||
|
|
||||||
|
long alreadyWritten = 0;
|
||||||
|
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
|
||||||
|
int length;
|
||||||
|
byte[] buffer = new byte[1 << 16];
|
||||||
|
while ((length = dataIn.read(buffer)) > 0) {
|
||||||
|
// Log.d(Constants.TAG, "read bytes: " + length);
|
||||||
|
if (out != null) {
|
||||||
|
out.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update signature buffer if signature is also present
|
||||||
|
if (signature != null) {
|
||||||
|
signature.update(buffer, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadyWritten += length;
|
||||||
|
if (wholeSize > 0) {
|
||||||
|
long progress = 100 * alreadyWritten / wholeSize;
|
||||||
|
// stop at 100% for wrong file sizes...
|
||||||
|
if (progress > 100) {
|
||||||
|
progress = 100;
|
||||||
|
}
|
||||||
|
progressScaler.setProgress((int) progress, 100);
|
||||||
|
}
|
||||||
|
// TODO: slow annealing to fake a progress?
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = new OpenPgpMetadata(
|
||||||
|
originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten);
|
||||||
|
|
||||||
|
if (signature != null) {
|
||||||
|
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||||
|
|
||||||
|
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
|
||||||
|
PGPSignature messageSignature = signatureList.get(signatureIndex);
|
||||||
|
|
||||||
|
// Verify signature
|
||||||
|
boolean validSignature = signature.verify(messageSignature);
|
||||||
|
if (validSignature) {
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||||
|
} else {
|
||||||
|
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for insecure hash algorithms
|
||||||
|
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
|
||||||
|
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
|
||||||
|
signatureResultBuilder.setInsecure(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureResultBuilder.setValidSignature(validSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
indent -= 1;
|
||||||
|
|
||||||
|
if (esResult != null) {
|
||||||
|
if (esResult.encryptedData.isIntegrityProtected()) {
|
||||||
|
updateProgress(R.string.progress_verifying_integrity, 95, 100);
|
||||||
|
|
||||||
|
if (esResult.encryptedData.verify()) {
|
||||||
|
log.add(LogType.MSG_DC_INTEGRITY_CHECK_OK, indent);
|
||||||
|
} else {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent);
|
||||||
|
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
} else if (signature == null) {
|
||||||
|
// If no signature is present, we *require* an MDC!
|
||||||
|
// Handle missing integrity protection like failed integrity protection!
|
||||||
|
// The MDC packet can be stripped by an attacker!
|
||||||
log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
|
log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
|
||||||
decryptionResultBuilder.setInsecure(true);
|
decryptionResultBuilder.setInsecure(true);
|
||||||
}
|
}
|
||||||
@@ -874,11 +660,280 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
|||||||
|
|
||||||
// Return a positive result, with metadata and verification info
|
// Return a positive result, with metadata and verification info
|
||||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||||
|
|
||||||
result.setCachedCryptoInputParcel(cryptoInput);
|
result.setCachedCryptoInputParcel(cryptoInput);
|
||||||
result.setSignatureResult(signatureResultBuilder.build());
|
result.setSignatureResult(signatureResultBuilder.build());
|
||||||
result.setCharset(charset);
|
result.setCharset(charset);
|
||||||
result.setDecryptionResult(decryptionResultBuilder.build());
|
result.setDecryptionResult(decryptionResultBuilder.build());
|
||||||
result.setDecryptionMetadata(metadata);
|
result.setDecryptionMetadata(metadata);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||||
|
PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress) throws PGPException {
|
||||||
|
|
||||||
|
// TODO is this necessary?
|
||||||
|
/*
|
||||||
|
else if (obj instanceof PGPEncryptedDataList) {
|
||||||
|
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
EncryptStreamResult result = new EncryptStreamResult();
|
||||||
|
|
||||||
|
boolean asymmetricPacketFound = false;
|
||||||
|
boolean symmetricPacketFound = false;
|
||||||
|
boolean anyPacketFound = false;
|
||||||
|
|
||||||
|
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||||
|
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||||
|
CanonicalizedSecretKey secretEncryptionKey = null;
|
||||||
|
|
||||||
|
Passphrase passphrase = null;
|
||||||
|
|
||||||
|
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||||
|
|
||||||
|
// go through all objects and find one we can decrypt
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Object obj = it.next();
|
||||||
|
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||||
|
anyPacketFound = true;
|
||||||
|
|
||||||
|
currentProgress += 2;
|
||||||
|
updateProgress(R.string.progress_finding_key, currentProgress, 100);
|
||||||
|
|
||||||
|
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||||
|
long subKeyId = encData.getKeyID();
|
||||||
|
|
||||||
|
log.add(LogType.MSG_DC_ASYM, indent,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||||
|
|
||||||
|
CanonicalizedSecretKeyRing secretKeyRing;
|
||||||
|
try {
|
||||||
|
// get actual keyring object based on master key id
|
||||||
|
secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(
|
||||||
|
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)
|
||||||
|
);
|
||||||
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
|
// continue with the next packet in the while loop
|
||||||
|
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (secretKeyRing == null) {
|
||||||
|
// continue with the next packet in the while loop
|
||||||
|
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow only specific keys for decryption?
|
||||||
|
if (input.getAllowedKeyIds() != null) {
|
||||||
|
long masterKeyId = secretKeyRing.getMasterKeyId();
|
||||||
|
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
|
||||||
|
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
|
||||||
|
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
||||||
|
|
||||||
|
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
|
||||||
|
// this key is in our db, but NOT allowed!
|
||||||
|
// continue with the next packet in the while loop
|
||||||
|
result.skippedDisallowedKey = true;
|
||||||
|
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get subkey which has been used for this encryption packet
|
||||||
|
secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
|
||||||
|
if (secretEncryptionKey == null) {
|
||||||
|
// should actually never happen, so no need to be more specific.
|
||||||
|
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* secret key exists in database and is allowed! */
|
||||||
|
asymmetricPacketFound = true;
|
||||||
|
|
||||||
|
encryptedDataAsymmetric = encData;
|
||||||
|
|
||||||
|
if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
|
||||||
|
passphrase = null;
|
||||||
|
} else if (cryptoInput.hasPassphrase()) {
|
||||||
|
passphrase = cryptoInput.getPassphrase();
|
||||||
|
} else {
|
||||||
|
// if no passphrase was explicitly set try to get it from the cache service
|
||||||
|
try {
|
||||||
|
// returns "" if key has no passphrase
|
||||||
|
passphrase = getCachedPassphrase(subKeyId);
|
||||||
|
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||||
|
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||||
|
if (passphrase == null) {
|
||||||
|
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(log,
|
||||||
|
RequiredInputParcel.createRequiredDecryptPassphrase(
|
||||||
|
secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
|
||||||
|
cryptoInput));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for insecure encryption key
|
||||||
|
if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) {
|
||||||
|
log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
|
||||||
|
result.insecureEncryptionKey = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// break out of while, only decrypt the first packet where we have a key
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else if (obj instanceof PGPPBEEncryptedData) {
|
||||||
|
anyPacketFound = true;
|
||||||
|
|
||||||
|
log.add(LogType.MSG_DC_SYM, indent);
|
||||||
|
|
||||||
|
if (!input.isAllowSymmetricDecryption()) {
|
||||||
|
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When mAllowSymmetricDecryption == true and we find a data packet here,
|
||||||
|
* we do not search for other available asymmetric packets!
|
||||||
|
*/
|
||||||
|
symmetricPacketFound = true;
|
||||||
|
|
||||||
|
encryptedDataSymmetric = (PGPPBEEncryptedData) obj;
|
||||||
|
|
||||||
|
// if no passphrase is given, return here
|
||||||
|
// indicating that a passphrase is missing!
|
||||||
|
if (!cryptoInput.hasPassphrase()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
passphrase = getCachedPassphrase(key.symmetric);
|
||||||
|
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||||
|
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||||
|
// nvm
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passphrase == null) {
|
||||||
|
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(log,
|
||||||
|
RequiredInputParcel.createRequiredSymmetricPassphrase(),
|
||||||
|
cryptoInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
passphrase = cryptoInput.getPassphrase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// break out of while, only decrypt the first packet
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More data, just acknowledge and ignore.
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Object obj = it.next();
|
||||||
|
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||||
|
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||||
|
long subKeyId = encData.getKeyID();
|
||||||
|
log.add(LogType.MSG_DC_TRAIL_ASYM, indent,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||||
|
} else if (obj instanceof PGPPBEEncryptedData) {
|
||||||
|
log.add(LogType.MSG_DC_TRAIL_SYM, indent);
|
||||||
|
} else {
|
||||||
|
log.add(LogType.MSG_DC_TRAIL_UNKNOWN, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we made sure above one of these two would be true
|
||||||
|
if (symmetricPacketFound) {
|
||||||
|
currentProgress += 2;
|
||||||
|
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||||
|
|
||||||
|
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
||||||
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
|
||||||
|
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
|
||||||
|
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||||
|
passphrase.getCharArray());
|
||||||
|
|
||||||
|
try {
|
||||||
|
result.cleartextStream = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
||||||
|
} catch (PGPDataValidationException e) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(log,
|
||||||
|
RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.encryptedData = encryptedDataSymmetric;
|
||||||
|
|
||||||
|
result.symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory);
|
||||||
|
} else if (asymmetricPacketFound) {
|
||||||
|
currentProgress += 2;
|
||||||
|
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
||||||
|
if (!secretEncryptionKey.unlock(passphrase)) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
|
}
|
||||||
|
} catch (PgpGeneralException e) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentProgress += 2;
|
||||||
|
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||||
|
|
||||||
|
CachingDataDecryptorFactory decryptorFactory
|
||||||
|
= secretEncryptionKey.getCachingDecryptorFactory(cryptoInput);
|
||||||
|
|
||||||
|
// special case: if the decryptor does not have a session key cached for this encrypted
|
||||||
|
// data, and can't actually decrypt on its own, return a pending intent
|
||||||
|
if (!decryptorFactory.canDecrypt()
|
||||||
|
&& !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
|
||||||
|
|
||||||
|
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
|
||||||
|
secretEncryptionKey.getRing().getMasterKeyId(),
|
||||||
|
secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
|
||||||
|
), cryptoInput));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
result.cleartextStream = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||||
|
} catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
||||||
|
result.encryptedData = encryptedDataAsymmetric;
|
||||||
|
|
||||||
|
cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// there wasn't even any useful data
|
||||||
|
if (!anyPacketFound) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_NO_DATA, log));
|
||||||
|
}
|
||||||
|
// there was data but key wasn't allowed
|
||||||
|
if (result.skippedDisallowedKey) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_KEY_DISALLOWED, log));
|
||||||
|
}
|
||||||
|
// no packet has been found where we have the corresponding secret key in our db
|
||||||
|
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@@ -116,32 +117,27 @@ public class PgpHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPgpContent(CharSequence input) {
|
public static String getPgpContent(@NonNull CharSequence input) {
|
||||||
// only decrypt if clipboard content is available and a pgp message or cleartext signature
|
Log.dEscaped(Constants.TAG, "input: " + input);
|
||||||
if (!TextUtils.isEmpty(input)) {
|
|
||||||
Log.dEscaped(Constants.TAG, "input: " + input);
|
|
||||||
|
|
||||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
|
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
String text = matcher.group(1);
|
||||||
|
text = fixPgpMessage(text);
|
||||||
|
|
||||||
|
Log.dEscaped(Constants.TAG, "input fixed: " + text);
|
||||||
|
return text;
|
||||||
|
} else {
|
||||||
|
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
String text = matcher.group(1);
|
String text = matcher.group(1);
|
||||||
text = fixPgpMessage(text);
|
text = fixPgpCleartextSignature(text);
|
||||||
|
|
||||||
Log.dEscaped(Constants.TAG, "input fixed: " + text);
|
Log.dEscaped(Constants.TAG, "input fixed: " + text);
|
||||||
return text;
|
return text;
|
||||||
} else {
|
} else {
|
||||||
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input);
|
return null;
|
||||||
if (matcher.matches()) {
|
|
||||||
String text = matcher.group(1);
|
|
||||||
text = fixPgpCleartextSignature(text);
|
|
||||||
|
|
||||||
Log.dEscaped(Constants.TAG, "input fixed: " + text);
|
|
||||||
return text;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.spongycastle.bcpg.HashAlgorithmTags;
|
|||||||
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,24 +43,23 @@ public class PgpSecurityConstants {
|
|||||||
* Whitelist of accepted symmetric encryption algorithms
|
* Whitelist of accepted symmetric encryption algorithms
|
||||||
* all other algorithms are rejected with OpenPgpDecryptionResult.RESULT_INSECURE
|
* all other algorithms are rejected with OpenPgpDecryptionResult.RESULT_INSECURE
|
||||||
*/
|
*/
|
||||||
private static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>();
|
private static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>(Arrays.asList(
|
||||||
static {
|
// General remarks: We try to keep the whitelist short to reduce attack surface
|
||||||
// General remarks: We try to keep the whitelist short to reduce attack surface
|
// TODO: block IDEA?: Bad key schedule (weak keys), implementation difficulties (easy to make errors)
|
||||||
// TODO: block IDEA?: Bad key schedule (weak keys), implementation difficulties (easy to make errors)
|
SymmetricKeyAlgorithmTags.IDEA,
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.IDEA);
|
SymmetricKeyAlgorithmTags.TRIPLE_DES, // a MUST in RFC
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TRIPLE_DES); // a MUST in RFC
|
SymmetricKeyAlgorithmTags.CAST5, // default in many gpg, pgp versions, 128 bit key
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.CAST5); // default in many gpg, pgp versions, 128 bit key
|
// BLOWFISH: Twofish is the successor
|
||||||
// BLOWFISH: Twofish is the successor
|
// SAFER: not used widely
|
||||||
// SAFER: not used widely
|
// DES: < 128 bit security
|
||||||
// DES: < 128 bit security
|
SymmetricKeyAlgorithmTags.AES_128,
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_128);
|
SymmetricKeyAlgorithmTags.AES_192,
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_192);
|
SymmetricKeyAlgorithmTags.AES_256,
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_256);
|
SymmetricKeyAlgorithmTags.TWOFISH // 128 bit
|
||||||
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TWOFISH); // 128 bit
|
// CAMELLIA_128: not used widely
|
||||||
// CAMELLIA_128: not used widely
|
// CAMELLIA_192: not used widely
|
||||||
// CAMELLIA_192: not used widely
|
// CAMELLIA_256: not used widely
|
||||||
// CAMELLIA_256: not used widely
|
));
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSecureSymmetricAlgorithm(int id) {
|
public static boolean isSecureSymmetricAlgorithm(int id) {
|
||||||
return sSymmetricAlgorithmsWhitelist.contains(id);
|
return sSymmetricAlgorithmsWhitelist.contains(id);
|
||||||
@@ -77,20 +77,19 @@ public class PgpSecurityConstants {
|
|||||||
* ((collision resistance of 112-bits))
|
* ((collision resistance of 112-bits))
|
||||||
* Implementations SHOULD NOT sign SHA-256 hashes. They MUST NOT default to signing SHA-256 hashes.
|
* Implementations SHOULD NOT sign SHA-256 hashes. They MUST NOT default to signing SHA-256 hashes.
|
||||||
*/
|
*/
|
||||||
private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>();
|
private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>(Arrays.asList(
|
||||||
static {
|
// MD5: broken
|
||||||
// MD5: broken
|
// SHA1: broken
|
||||||
// SHA1: broken
|
// RIPEMD160: same security properties as SHA1
|
||||||
// RIPEMD160: same security properties as SHA1
|
// DOUBLE_SHA: not used widely
|
||||||
// DOUBLE_SHA: not used widely
|
// MD2: not used widely
|
||||||
// MD2: not used widely
|
// TIGER_192: not used widely
|
||||||
// TIGER_192: not used widely
|
// HAVAL_5_160: not used widely
|
||||||
// HAVAL_5_160: not used widely
|
HashAlgorithmTags.SHA256, // compatibility for old Mailvelope versions
|
||||||
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA256); // compatibility for old Mailvelope versions
|
HashAlgorithmTags.SHA384,
|
||||||
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA384);
|
HashAlgorithmTags.SHA512
|
||||||
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA512);
|
// SHA224: Not used widely, Yahoo argues against it
|
||||||
// SHA224: Not used widely, Yahoo argues against it
|
));
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSecureHashAlgorithm(int id) {
|
public static boolean isSecureHashAlgorithm(int id) {
|
||||||
return sHashAlgorithmsWhitelist.contains(id);
|
return sHashAlgorithmsWhitelist.contains(id);
|
||||||
@@ -106,12 +105,11 @@ public class PgpSecurityConstants {
|
|||||||
* bitlength less than 1023 bits.
|
* bitlength less than 1023 bits.
|
||||||
* Implementations MUST NOT accept any RSA keys with bitlength less than 2047 bits after January 1, 2016.
|
* Implementations MUST NOT accept any RSA keys with bitlength less than 2047 bits after January 1, 2016.
|
||||||
*/
|
*/
|
||||||
private static HashSet<String> sCurveWhitelist = new HashSet<>();
|
private static HashSet<String> sCurveWhitelist = new HashSet<>(Arrays.asList(
|
||||||
static {
|
NISTNamedCurves.getOID("P-256").getId(),
|
||||||
sCurveWhitelist.add(NISTNamedCurves.getOID("P-256").getId());
|
NISTNamedCurves.getOID("P-384").getId(),
|
||||||
sCurveWhitelist.add(NISTNamedCurves.getOID("P-384").getId());
|
NISTNamedCurves.getOID("P-521").getId()
|
||||||
sCurveWhitelist.add(NISTNamedCurves.getOID("P-521").getId());
|
));
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isSecureKey(CanonicalizedPublicKey key) {
|
public static boolean isSecureKey(CanonicalizedPublicKey key) {
|
||||||
switch (key.getAlgorithm()) {
|
switch (key.getAlgorithm()) {
|
||||||
|
|||||||
@@ -216,17 +216,6 @@ public class UncachedKeyRing implements Serializable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsSubkey(String expectedFingerprint) {
|
|
||||||
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
if (KeyFormattingUtils.convertFingerprintToHex(
|
|
||||||
it.next().getFingerprint()).equalsIgnoreCase(expectedFingerprint)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IteratorWithIOThrow<E> {
|
public interface IteratorWithIOThrow<E> {
|
||||||
public boolean hasNext() throws IOException;
|
public boolean hasNext() throws IOException;
|
||||||
public E next() throws IOException;
|
public E next() throws IOException;
|
||||||
|
|||||||
@@ -368,4 +368,5 @@ public class UncachedPublicKey {
|
|||||||
|
|
||||||
return calendar.getTime();
|
return calendar.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
|
<<<<<<< HEAD
|
||||||
|
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
=======
|
||||||
|
>>>>>>> development
|
||||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@@ -37,6 +41,7 @@ public class WrappedUserAttribute implements Serializable {
|
|||||||
|
|
||||||
public static final int UAT_NONE = 0;
|
public static final int UAT_NONE = 0;
|
||||||
public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE;
|
public static final int UAT_IMAGE = UserAttributeSubpacketTags.IMAGE_ATTRIBUTE;
|
||||||
|
public static final int UAT_URI_ATTRIBUTE = 101;
|
||||||
|
|
||||||
private PGPUserAttributeSubpacketVector mVector;
|
private PGPUserAttributeSubpacketVector mVector;
|
||||||
|
|
||||||
@@ -77,7 +82,7 @@ public class WrappedUserAttribute implements Serializable {
|
|||||||
public static WrappedUserAttribute fromData (byte[] data) throws IOException {
|
public static WrappedUserAttribute fromData (byte[] data) throws IOException {
|
||||||
UserAttributeSubpacketInputStream in =
|
UserAttributeSubpacketInputStream in =
|
||||||
new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data));
|
new UserAttributeSubpacketInputStream(new ByteArrayInputStream(data));
|
||||||
ArrayList<UserAttributeSubpacket> list = new ArrayList<UserAttributeSubpacket>();
|
ArrayList<UserAttributeSubpacket> list = new ArrayList<>();
|
||||||
while (in.available() > 0) {
|
while (in.available() > 0) {
|
||||||
list.add(in.readPacket());
|
list.add(in.readPacket());
|
||||||
}
|
}
|
||||||
@@ -121,6 +126,7 @@ public class WrappedUserAttribute implements Serializable {
|
|||||||
private void readObjectNoData() throws ObjectStreamException {
|
private void readObjectNoData() throws ObjectStreamException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SimplifiableIfStatement")
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!WrappedUserAttribute.class.isInstance(o)) {
|
if (!WrappedUserAttribute.class.isInstance(o)) {
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ public class KeychainContract {
|
|||||||
String EXPIRY = "expiry";
|
String EXPIRY = "expiry";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpdatedKeysColumns {
|
||||||
|
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||||
|
String LAST_UPDATED = "last_updated"; // time since epoch in seconds
|
||||||
|
}
|
||||||
|
|
||||||
interface UserPacketsColumns {
|
interface UserPacketsColumns {
|
||||||
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
|
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
|
||||||
String TYPE = "type"; // not a database id
|
String TYPE = "type"; // not a database id
|
||||||
@@ -97,6 +102,8 @@ public class KeychainContract {
|
|||||||
|
|
||||||
public static final String BASE_KEY_RINGS = "key_rings";
|
public static final String BASE_KEY_RINGS = "key_rings";
|
||||||
|
|
||||||
|
public static final String BASE_UPDATED_KEYS = "updated_keys";
|
||||||
|
|
||||||
public static final String PATH_UNIFIED = "unified";
|
public static final String PATH_UNIFIED = "unified";
|
||||||
|
|
||||||
public static final String PATH_FIND = "find";
|
public static final String PATH_FIND = "find";
|
||||||
@@ -106,6 +113,7 @@ public class KeychainContract {
|
|||||||
public static final String PATH_PUBLIC = "public";
|
public static final String PATH_PUBLIC = "public";
|
||||||
public static final String PATH_SECRET = "secret";
|
public static final String PATH_SECRET = "secret";
|
||||||
public static final String PATH_USER_IDS = "user_ids";
|
public static final String PATH_USER_IDS = "user_ids";
|
||||||
|
public static final String PATH_LINKED_IDS = "linked_ids";
|
||||||
public static final String PATH_KEYS = "keys";
|
public static final String PATH_KEYS = "keys";
|
||||||
public static final String PATH_CERTS = "certs";
|
public static final String PATH_CERTS = "certs";
|
||||||
|
|
||||||
@@ -234,6 +242,16 @@ public class KeychainContract {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class UpdatedKeys implements UpdatedKeysColumns, BaseColumns {
|
||||||
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
|
.appendPath(BASE_UPDATED_KEYS).build();
|
||||||
|
|
||||||
|
public static final String CONTENT_TYPE
|
||||||
|
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
|
||||||
|
public static final String CONTENT_ITEM_TYPE
|
||||||
|
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.updated_keys";
|
||||||
|
}
|
||||||
|
|
||||||
public static class UserPackets implements UserPacketsColumns, BaseColumns {
|
public static class UserPackets implements UserPacketsColumns, BaseColumns {
|
||||||
public static final String VERIFIED = "verified";
|
public static final String VERIFIED = "verified";
|
||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
@@ -262,6 +280,11 @@ public class KeychainContract {
|
|||||||
public static Uri buildUserIdsUri(Uri uri) {
|
public static Uri buildUserIdsUri(Uri uri) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
|
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri buildLinkedIdsUri(Uri uri) {
|
||||||
|
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_LINKED_IDS).build();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ApiApps implements ApiAppsColumns, BaseColumns {
|
public static class ApiApps implements ApiAppsColumns, BaseColumns {
|
||||||
@@ -350,7 +373,14 @@ public class KeychainContract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Uri buildCertsUri(Uri uri) {
|
public static Uri buildCertsUri(Uri uri) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build();
|
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1))
|
||||||
|
.appendPath(PATH_CERTS).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri buildLinkedIdCertsUri(Uri uri, int rank) {
|
||||||
|
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1))
|
||||||
|
.appendPath(PATH_LINKED_IDS).appendPath(Integer.toString(rank))
|
||||||
|
.appendPath(PATH_CERTS).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
||||||
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
|
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -53,7 +54,7 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class KeychainDatabase extends SQLiteOpenHelper {
|
public class KeychainDatabase extends SQLiteOpenHelper {
|
||||||
private static final String DATABASE_NAME = "openkeychain.db";
|
private static final String DATABASE_NAME = "openkeychain.db";
|
||||||
private static final int DATABASE_VERSION = 11;
|
private static final int DATABASE_VERSION = 12;
|
||||||
static Boolean apgHack = false;
|
static Boolean apgHack = false;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
String KEY_RINGS_PUBLIC = "keyrings_public";
|
String KEY_RINGS_PUBLIC = "keyrings_public";
|
||||||
String KEY_RINGS_SECRET = "keyrings_secret";
|
String KEY_RINGS_SECRET = "keyrings_secret";
|
||||||
String KEYS = "keys";
|
String KEYS = "keys";
|
||||||
|
String UPDATED_KEYS = "updated_keys";
|
||||||
String USER_PACKETS = "user_packets";
|
String USER_PACKETS = "user_packets";
|
||||||
String CERTS = "certs";
|
String CERTS = "certs";
|
||||||
String API_APPS = "api_apps";
|
String API_APPS = "api_apps";
|
||||||
@@ -144,6 +146,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
|
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
|
private static final String CREATE_UPDATE_KEYS =
|
||||||
|
"CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " ("
|
||||||
|
+ UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, "
|
||||||
|
+ UpdatedKeysColumns.LAST_UPDATED + " INTEGER, "
|
||||||
|
+ "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES "
|
||||||
|
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
|
||||||
|
+ ")";
|
||||||
|
|
||||||
private static final String CREATE_API_APPS =
|
private static final String CREATE_API_APPS =
|
||||||
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
|
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
|
||||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
@@ -206,6 +216,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
db.execSQL(CREATE_KEYS);
|
db.execSQL(CREATE_KEYS);
|
||||||
db.execSQL(CREATE_USER_PACKETS);
|
db.execSQL(CREATE_USER_PACKETS);
|
||||||
db.execSQL(CREATE_CERTS);
|
db.execSQL(CREATE_CERTS);
|
||||||
|
db.execSQL(CREATE_UPDATE_KEYS);
|
||||||
db.execSQL(CREATE_API_APPS);
|
db.execSQL(CREATE_API_APPS);
|
||||||
db.execSQL(CREATE_API_APPS_ACCOUNTS);
|
db.execSQL(CREATE_API_APPS_ACCOUNTS);
|
||||||
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
||||||
@@ -278,8 +289,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
// fix problems in database, see #1402 for details
|
// fix problems in database, see #1402 for details
|
||||||
// https://github.com/open-keychain/open-keychain/issues/1402
|
// https://github.com/open-keychain/open-keychain/issues/1402
|
||||||
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
|
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
|
||||||
|
case 12:
|
||||||
|
db.execSQL(CREATE_UPDATE_KEYS);
|
||||||
if (oldVersion == 10) {
|
if (oldVersion == 10) {
|
||||||
// no consolidate if we are updating from 10, we're just here for the api_accounts fix
|
// no consolidate if we are updating from 10, we're just here for
|
||||||
|
// the api_accounts fix and the new update keys table
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.net.Uri;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
@@ -38,6 +39,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||||
@@ -62,6 +64,8 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
private static final int KEY_RING_SECRET = 204;
|
private static final int KEY_RING_SECRET = 204;
|
||||||
private static final int KEY_RING_CERTS = 205;
|
private static final int KEY_RING_CERTS = 205;
|
||||||
private static final int KEY_RING_CERTS_SPECIFIC = 206;
|
private static final int KEY_RING_CERTS_SPECIFIC = 206;
|
||||||
|
private static final int KEY_RING_LINKED_IDS = 207;
|
||||||
|
private static final int KEY_RING_LINKED_ID_CERTS = 208;
|
||||||
|
|
||||||
private static final int API_APPS = 301;
|
private static final int API_APPS = 301;
|
||||||
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
||||||
@@ -72,6 +76,9 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
|
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
|
||||||
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
|
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
|
||||||
|
|
||||||
|
private static final int UPDATED_KEYS = 500;
|
||||||
|
private static final int UPDATED_KEYS_SPECIFIC = 501;
|
||||||
|
|
||||||
protected UriMatcher mUriMatcher;
|
protected UriMatcher mUriMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,6 +134,9 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
* key_rings/_/unified
|
* key_rings/_/unified
|
||||||
* key_rings/_/keys
|
* key_rings/_/keys
|
||||||
* key_rings/_/user_ids
|
* key_rings/_/user_ids
|
||||||
|
* key_rings/_/linked_ids
|
||||||
|
* key_rings/_/linked_ids/_
|
||||||
|
* key_rings/_/linked_ids/_/certs
|
||||||
* key_rings/_/public
|
* key_rings/_/public
|
||||||
* key_rings/_/secret
|
* key_rings/_/secret
|
||||||
* key_rings/_/certs
|
* key_rings/_/certs
|
||||||
@@ -142,6 +152,13 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
||||||
+ KeychainContract.PATH_USER_IDS,
|
+ KeychainContract.PATH_USER_IDS,
|
||||||
KEY_RING_USER_IDS);
|
KEY_RING_USER_IDS);
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
||||||
|
+ KeychainContract.PATH_LINKED_IDS,
|
||||||
|
KEY_RING_LINKED_IDS);
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
||||||
|
+ KeychainContract.PATH_LINKED_IDS + "/*/"
|
||||||
|
+ KeychainContract.PATH_CERTS,
|
||||||
|
KEY_RING_LINKED_ID_CERTS);
|
||||||
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/"
|
||||||
+ KeychainContract.PATH_PUBLIC,
|
+ KeychainContract.PATH_PUBLIC,
|
||||||
KEY_RING_PUBLIC);
|
KEY_RING_PUBLIC);
|
||||||
@@ -179,6 +196,12 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
|
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
|
||||||
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
|
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to access table containing last updated dates of keys
|
||||||
|
*/
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS, UPDATED_KEYS);
|
||||||
|
matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS + "/*", UPDATED_KEYS_SPECIFIC);
|
||||||
|
|
||||||
return matcher;
|
return matcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +241,11 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
case KEY_RING_SECRET:
|
case KEY_RING_SECRET:
|
||||||
return KeyRings.CONTENT_ITEM_TYPE;
|
return KeyRings.CONTENT_ITEM_TYPE;
|
||||||
|
|
||||||
|
case UPDATED_KEYS:
|
||||||
|
return UpdatedKeys.CONTENT_TYPE;
|
||||||
|
case UPDATED_KEYS_SPECIFIC:
|
||||||
|
return UpdatedKeys.CONTENT_ITEM_TYPE;
|
||||||
|
|
||||||
case API_APPS:
|
case API_APPS:
|
||||||
return ApiApps.CONTENT_TYPE;
|
return ApiApps.CONTENT_TYPE;
|
||||||
|
|
||||||
@@ -477,7 +505,8 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case KEY_RINGS_USER_IDS:
|
case KEY_RINGS_USER_IDS:
|
||||||
case KEY_RING_USER_IDS: {
|
case KEY_RING_USER_IDS:
|
||||||
|
case KEY_RING_LINKED_IDS: {
|
||||||
HashMap<String, String> projectionMap = new HashMap<>();
|
HashMap<String, String> projectionMap = new HashMap<>();
|
||||||
projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id");
|
projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id");
|
||||||
projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID);
|
projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID);
|
||||||
@@ -502,13 +531,15 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
|
groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
|
||||||
+ ", " + Tables.USER_PACKETS + "." + UserPackets.RANK;
|
+ ", " + Tables.USER_PACKETS + "." + UserPackets.RANK;
|
||||||
|
|
||||||
// for now, we only respect user ids here, so TYPE must be NULL
|
if (match == KEY_RING_LINKED_IDS) {
|
||||||
// TODO expand with KEY_RING_USER_PACKETS query type which lifts this restriction
|
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " = "
|
||||||
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL");
|
+ WrappedUserAttribute.UAT_URI_ATTRIBUTE);
|
||||||
|
} else {
|
||||||
|
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL");
|
||||||
|
}
|
||||||
|
|
||||||
// If we are searching for a particular keyring's ids, add where
|
// If we are searching for a particular keyring's ids, add where
|
||||||
if (match == KEY_RING_USER_IDS) {
|
if (match == KEY_RING_USER_IDS || match == KEY_RING_LINKED_IDS) {
|
||||||
// TODO remove with the thing above
|
|
||||||
qb.appendWhere(" AND ");
|
qb.appendWhere(" AND ");
|
||||||
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = ");
|
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = ");
|
||||||
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
|
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
|
||||||
@@ -559,7 +590,8 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case KEY_RING_CERTS:
|
case KEY_RING_CERTS:
|
||||||
case KEY_RING_CERTS_SPECIFIC: {
|
case KEY_RING_CERTS_SPECIFIC:
|
||||||
|
case KEY_RING_LINKED_ID_CERTS: {
|
||||||
HashMap<String, String> projectionMap = new HashMap<>();
|
HashMap<String, String> projectionMap = new HashMap<>();
|
||||||
projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID);
|
projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID);
|
||||||
projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID);
|
projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID);
|
||||||
@@ -580,10 +612,6 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
+ " AND "
|
+ " AND "
|
||||||
+ Tables.CERTS + "." + Certs.RANK + " = "
|
+ Tables.CERTS + "." + Certs.RANK + " = "
|
||||||
+ Tables.USER_PACKETS + "." + UserPackets.RANK
|
+ Tables.USER_PACKETS + "." + UserPackets.RANK
|
||||||
// for now, we only return user ids here, so TYPE must be NULL
|
|
||||||
// TODO at some point, we should lift this restriction
|
|
||||||
+ " AND "
|
|
||||||
+ Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"
|
|
||||||
+ ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON ("
|
+ ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON ("
|
||||||
+ Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = "
|
+ Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = "
|
||||||
+ "signer." + UserPackets.MASTER_KEY_ID
|
+ "signer." + UserPackets.MASTER_KEY_ID
|
||||||
@@ -603,6 +631,33 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
qb.appendWhereEscapeString(uri.getPathSegments().get(4));
|
qb.appendWhereEscapeString(uri.getPathSegments().get(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (match == KEY_RING_LINKED_ID_CERTS) {
|
||||||
|
qb.appendWhere(" AND " + Tables.USER_PACKETS + "."
|
||||||
|
+ UserPackets.TYPE + " IS NOT NULL");
|
||||||
|
|
||||||
|
qb.appendWhere(" AND " + Tables.USER_PACKETS + "."
|
||||||
|
+ UserPackets.RANK + " = ");
|
||||||
|
qb.appendWhereEscapeString(uri.getPathSegments().get(3));
|
||||||
|
} else {
|
||||||
|
qb.appendWhere(" AND " + Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATED_KEYS:
|
||||||
|
case UPDATED_KEYS_SPECIFIC: {
|
||||||
|
HashMap<String, String> projectionMap = new HashMap<>();
|
||||||
|
qb.setTables(Tables.UPDATED_KEYS);
|
||||||
|
projectionMap.put(UpdatedKeys.MASTER_KEY_ID, Tables.UPDATED_KEYS + "."
|
||||||
|
+ UpdatedKeys.MASTER_KEY_ID);
|
||||||
|
projectionMap.put(UpdatedKeys.LAST_UPDATED, Tables.UPDATED_KEYS + "."
|
||||||
|
+ UpdatedKeys.LAST_UPDATED);
|
||||||
|
qb.setProjectionMap(projectionMap);
|
||||||
|
if (match == UPDATED_KEYS_SPECIFIC) {
|
||||||
|
qb.appendWhere(UpdatedKeys.MASTER_KEY_ID + " = ");
|
||||||
|
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,6 +781,12 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
|
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case UPDATED_KEYS: {
|
||||||
|
long updatedKeyId = db.replace(Tables.UPDATED_KEYS, null, values);
|
||||||
|
rowUri = UpdatedKeys.CONTENT_URI.buildUpon().appendPath("" + updatedKeyId)
|
||||||
|
.build();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case API_APPS: {
|
case API_APPS: {
|
||||||
db.insertOrThrow(Tables.API_APPS, null, values);
|
db.insertOrThrow(Tables.API_APPS, null, values);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -32,10 +32,15 @@ import android.support.v4.util.LongSparseArray;
|
|||||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
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.ImportKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||||
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
||||||
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.SaveKeyringResult;
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
@@ -50,7 +55,6 @@ import org.sufficientlysecure.keychain.pgp.Progressable;
|
|||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
@@ -59,14 +63,12 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
|
||||||
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.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;
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
|
||||||
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
import org.sufficientlysecure.keychain.util.Utf8Util;
|
import org.sufficientlysecure.keychain.util.Utf8Util;
|
||||||
@@ -82,6 +84,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains high level methods for database access. Despite its
|
* This class contains high level methods for database access. Despite its
|
||||||
@@ -685,6 +688,36 @@ public class ProviderHelper {
|
|||||||
mIndent -= 1;
|
mIndent -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// before deleting key, retrieve it's last updated time
|
||||||
|
final int INDEX_MASTER_KEY_ID = 0;
|
||||||
|
final int INDEX_LAST_UPDATED = 1;
|
||||||
|
Cursor lastUpdatedCursor = mContentResolver.query(
|
||||||
|
UpdatedKeys.CONTENT_URI,
|
||||||
|
new String[]{
|
||||||
|
UpdatedKeys.MASTER_KEY_ID,
|
||||||
|
UpdatedKeys.LAST_UPDATED
|
||||||
|
},
|
||||||
|
UpdatedKeys.MASTER_KEY_ID + " = ?",
|
||||||
|
new String[]{"" + masterKeyId},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
if (lastUpdatedCursor.moveToNext()) {
|
||||||
|
// there was an entry to re-insert
|
||||||
|
// this operation must happen after the new key is inserted
|
||||||
|
ContentValues lastUpdatedEntry = new ContentValues(2);
|
||||||
|
lastUpdatedEntry.put(UpdatedKeys.MASTER_KEY_ID,
|
||||||
|
lastUpdatedCursor.getLong(INDEX_MASTER_KEY_ID));
|
||||||
|
lastUpdatedEntry.put(UpdatedKeys.LAST_UPDATED,
|
||||||
|
lastUpdatedCursor.getLong(INDEX_LAST_UPDATED));
|
||||||
|
operations.add(
|
||||||
|
ContentProviderOperation
|
||||||
|
.newInsert(UpdatedKeys.CONTENT_URI)
|
||||||
|
.withValues(lastUpdatedEntry)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lastUpdatedCursor.close();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
|
||||||
int deleted = mContentResolver.delete(
|
int deleted = mContentResolver.delete(
|
||||||
@@ -738,6 +771,11 @@ public class ProviderHelper {
|
|||||||
if (type != o.type) {
|
if (type != o.type) {
|
||||||
return type == null ? -1 : 1;
|
return type == null ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
// if one is *trusted* but the other isn't, that one comes first
|
||||||
|
// this overrides the primary attribute, even!
|
||||||
|
if ( (trustedCerts.size() == 0) != (o.trustedCerts.size() == 0) ) {
|
||||||
|
return trustedCerts.size() > o.trustedCerts.size() ? -1 : 1;
|
||||||
|
}
|
||||||
// if one key is primary but the other isn't, the primary one always comes first
|
// if one key is primary but the other isn't, the primary one always comes first
|
||||||
if (isPrimary != o.isPrimary) {
|
if (isPrimary != o.isPrimary) {
|
||||||
return isPrimary ? -1 : 1;
|
return isPrimary ? -1 : 1;
|
||||||
@@ -845,7 +883,7 @@ public class ProviderHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
|
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
|
||||||
return savePublicKeyRing(keyRing, new ProgressScaler());
|
return savePublicKeyRing(keyRing, new ProgressScaler(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -854,7 +892,7 @@ public class ProviderHelper {
|
|||||||
* This is a high level method, which takes care of merging all new information into the old and
|
* This is a high level method, which takes care of merging all new information into the old and
|
||||||
* keep public and secret keyrings in sync.
|
* keep public and secret keyrings in sync.
|
||||||
*/
|
*/
|
||||||
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress) {
|
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress, String expectedFingerprint) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
long masterKeyId = publicRing.getMasterKeyId();
|
long masterKeyId = publicRing.getMasterKeyId();
|
||||||
@@ -927,6 +965,17 @@ public class ProviderHelper {
|
|||||||
canSecretRing = null;
|
canSecretRing = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If we have an expected fingerprint, make sure it matches
|
||||||
|
if (expectedFingerprint != null) {
|
||||||
|
if (!canPublicRing.containsSubkey(expectedFingerprint)) {
|
||||||
|
log(LogType.MSG_IP_FINGERPRINT_ERROR);
|
||||||
|
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||||
|
} else {
|
||||||
|
log(LogType.MSG_IP_FINGERPRINT_OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
|
int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
|
||||||
|
|
||||||
// Save the saved keyring (if any)
|
// Save the saved keyring (if any)
|
||||||
@@ -1239,6 +1288,28 @@ public class ProviderHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. wipe database (IT'S DANGEROUS)
|
// 2. wipe database (IT'S DANGEROUS)
|
||||||
|
|
||||||
|
// first, backup our list of updated key times
|
||||||
|
ArrayList<ContentValues> updatedKeysValues = new ArrayList<>();
|
||||||
|
final int INDEX_MASTER_KEY_ID = 0;
|
||||||
|
final int INDEX_LAST_UPDATED = 1;
|
||||||
|
Cursor lastUpdatedCursor = mContentResolver.query(
|
||||||
|
UpdatedKeys.CONTENT_URI,
|
||||||
|
new String[]{
|
||||||
|
UpdatedKeys.MASTER_KEY_ID,
|
||||||
|
UpdatedKeys.LAST_UPDATED
|
||||||
|
},
|
||||||
|
null, null, null);
|
||||||
|
while (lastUpdatedCursor.moveToNext()) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(UpdatedKeys.MASTER_KEY_ID,
|
||||||
|
lastUpdatedCursor.getLong(INDEX_MASTER_KEY_ID));
|
||||||
|
values.put(UpdatedKeys.LAST_UPDATED,
|
||||||
|
lastUpdatedCursor.getLong(INDEX_LAST_UPDATED));
|
||||||
|
updatedKeysValues.add(values);
|
||||||
|
}
|
||||||
|
lastUpdatedCursor.close();
|
||||||
|
|
||||||
log.add(LogType.MSG_CON_DB_CLEAR, indent);
|
log.add(LogType.MSG_CON_DB_CLEAR, indent);
|
||||||
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
|
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
|
||||||
|
|
||||||
@@ -1288,6 +1359,10 @@ public class ProviderHelper {
|
|||||||
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
|
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
|
||||||
.serialKeyRingImport(itPublics, numPublics, null, null);
|
.serialKeyRingImport(itPublics, numPublics, null, null);
|
||||||
log.add(result, indent);
|
log.add(result, indent);
|
||||||
|
// re-insert our backed up list of updated key times
|
||||||
|
// TODO: can this cause issues in case a public key re-import failed?
|
||||||
|
mContentResolver.bulkInsert(UpdatedKeys.CONTENT_URI,
|
||||||
|
updatedKeysValues.toArray(new ContentValues[updatedKeysValues.size()]));
|
||||||
} else {
|
} else {
|
||||||
log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent);
|
log.add(LogType.MSG_CON_REIMPORT_PUBLIC_SKIP, indent);
|
||||||
}
|
}
|
||||||
@@ -1397,6 +1472,14 @@ public class ProviderHelper {
|
|||||||
return getKeyRingAsArmoredString(data);
|
return getKeyRingAsArmoredString(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Uri renewKeyLastUpdatedTime(long masterKeyId, long time, TimeUnit timeUnit) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId);
|
||||||
|
values.put(UpdatedKeys.LAST_UPDATED, timeUnit.toSeconds(time));
|
||||||
|
|
||||||
|
return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values);
|
||||||
|
}
|
||||||
|
|
||||||
public ArrayList<String> getRegisteredApiApps() {
|
public ArrayList<String> getRegisteredApiApps() {
|
||||||
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
|
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.net.Uri;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.openintents.openpgp.IOpenPgpService;
|
import org.openintents.openpgp.IOpenPgpService;
|
||||||
@@ -38,7 +39,7 @@ 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.PgpSecurityConstants;
|
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||||
@@ -65,8 +66,10 @@ 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.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class OpenPgpService extends RemoteService {
|
public class OpenPgpService extends RemoteService {
|
||||||
|
|
||||||
@@ -533,7 +536,7 @@ public class OpenPgpService extends RemoteService {
|
|||||||
|
|
||||||
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
||||||
|
|
||||||
PgpDecryptVerify op = new PgpDecryptVerify(this, mProviderHelper, null);
|
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mProviderHelper, null);
|
||||||
|
|
||||||
long inputLength = inputStream.available();
|
long inputLength = inputStream.available();
|
||||||
InputData inputData = new InputData(inputStream, inputLength);
|
InputData inputData = new InputData(inputStream, inputLength);
|
||||||
@@ -715,28 +718,40 @@ public class OpenPgpService extends RemoteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent getSignKeyIdImpl(Intent data) {
|
private Intent getSignKeyIdImpl(Intent data) {
|
||||||
String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
|
// if data already contains EXTRA_SIGN_KEY_ID, it has been executed again
|
||||||
|
// after user interaction. Then, we just need to return the long again!
|
||||||
|
if (data.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
|
||||||
|
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID,
|
||||||
|
Constants.key.none);
|
||||||
|
|
||||||
Intent intent = new Intent(getBaseContext(), SelectSignKeyIdActivity.class);
|
Intent result = new Intent();
|
||||||
String currentPkg = getCurrentCallingPackage();
|
result.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, signKeyId);
|
||||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(currentPkg));
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
|
return result;
|
||||||
intent.putExtra(SelectSignKeyIdActivity.EXTRA_DATA, data);
|
} else {
|
||||||
|
String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
Intent intent = new Intent(getBaseContext(), SelectSignKeyIdActivity.class);
|
||||||
intent,
|
String currentPkg = getCurrentCallingPackage();
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(currentPkg));
|
||||||
|
intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
|
||||||
|
intent.putExtra(SelectSignKeyIdActivity.EXTRA_DATA, data);
|
||||||
|
|
||||||
// return PendingIntent to be executed by client
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
Intent result = new Intent();
|
intent,
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
|
||||||
|
|
||||||
return result;
|
// return PendingIntent to be executed by client
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getKeyIdsImpl(Intent data) {
|
private Intent getKeyIdsImpl(Intent data) {
|
||||||
// if data already contains key ids extra GET_KEY_IDS has been executed again
|
// if data already contains EXTRA_KEY_IDS, it has been executed again
|
||||||
// after user interaction. Then, we just need to return the array again!
|
// after user interaction. Then, we just need to return the array again!
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||||
long[] keyIdsArray = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
long[] keyIdsArray = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||||
@@ -800,19 +815,14 @@ public class OpenPgpService extends RemoteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// version code is required and needs to correspond to version code of service!
|
// version code is required and needs to correspond to version code of service!
|
||||||
// History of versions in org.openintents.openpgp.util.OpenPgpApi
|
// History of versions in openpgp-api's CHANGELOG.md
|
||||||
// we support 3, 4, 5, 6
|
List<Integer> supportedVersions = Arrays.asList(3, 4, 5, 6, 7, 8, 9);
|
||||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 3
|
if (!supportedVersions.contains(data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1))) {
|
||||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 4
|
|
||||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5
|
|
||||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 6
|
|
||||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 7
|
|
||||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 8) {
|
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
OpenPgpError error = new OpenPgpError
|
OpenPgpError error = new OpenPgpError
|
||||||
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
|
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
|
||||||
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
|
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
|
||||||
+ "supported API versions: 3-8");
|
+ "supported API versions: " + supportedVersions.toString());
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
return result;
|
return result;
|
||||||
@@ -830,67 +840,8 @@ public class OpenPgpService extends RemoteService {
|
|||||||
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 {
|
Log.w(Constants.TAG, "You are using a deprecated service which may lead to truncated data on return, please use IOpenPgpService2!");
|
||||||
Intent errorResult = checkRequirements(data);
|
return executeInternal(data, input, output);
|
||||||
if (errorResult != null) {
|
|
||||||
return errorResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
String action = data.getAction();
|
|
||||||
switch (action) {
|
|
||||||
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
|
||||||
return signImpl(data, input, output, true);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_SIGN: {
|
|
||||||
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
|
||||||
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
|
||||||
return signImpl(data, input, output, true);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_DETACHED_SIGN: {
|
|
||||||
return signImpl(data, input, output, false);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_ENCRYPT: {
|
|
||||||
return encryptAndSignImpl(data, input, output, false);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
|
|
||||||
return encryptAndSignImpl(data, input, output, true);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
|
|
||||||
return decryptAndVerifyImpl(data, input, output, false);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
|
|
||||||
return decryptAndVerifyImpl(data, input, output, true);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
|
|
||||||
return getSignKeyIdImpl(data);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_GET_KEY_IDS: {
|
|
||||||
return getKeyIdsImpl(data);
|
|
||||||
}
|
|
||||||
case OpenPgpApi.ACTION_GET_KEY: {
|
|
||||||
return getKeyImpl(data);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// always close input and output file descriptors even in error cases
|
|
||||||
if (input != null) {
|
|
||||||
try {
|
|
||||||
input.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (output != null) {
|
|
||||||
try {
|
|
||||||
output.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -900,4 +851,68 @@ public class OpenPgpService extends RemoteService {
|
|||||||
return mBinder;
|
return mBinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Intent executeInternal(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||||
|
try {
|
||||||
|
Intent errorResult = checkRequirements(data);
|
||||||
|
if (errorResult != null) {
|
||||||
|
return errorResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
String action = data.getAction();
|
||||||
|
switch (action) {
|
||||||
|
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
||||||
|
return signImpl(data, input, output, true);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_SIGN: {
|
||||||
|
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
||||||
|
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
||||||
|
return signImpl(data, input, output, true);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_DETACHED_SIGN: {
|
||||||
|
return signImpl(data, input, output, false);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_ENCRYPT: {
|
||||||
|
return encryptAndSignImpl(data, input, output, false);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
|
||||||
|
return encryptAndSignImpl(data, input, output, true);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
|
||||||
|
return decryptAndVerifyImpl(data, input, output, false);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_DECRYPT_METADATA: {
|
||||||
|
return decryptAndVerifyImpl(data, input, output, true);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
|
||||||
|
return getSignKeyIdImpl(data);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_GET_KEY_IDS: {
|
||||||
|
return getKeyIdsImpl(data);
|
||||||
|
}
|
||||||
|
case OpenPgpApi.ACTION_GET_KEY: {
|
||||||
|
return getKeyImpl(data);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// always close input and output file descriptors even in error cases
|
||||||
|
if (input != null) {
|
||||||
|
try {
|
||||||
|
input.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (output != null) {
|
||||||
|
try {
|
||||||
|
output.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Intent;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
|
import org.openintents.openpgp.IOpenPgpService2;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class OpenPgpService2 extends OpenPgpService {
|
||||||
|
|
||||||
|
private Map<Long, ParcelFileDescriptor> mOutputPipeMap = new HashMap<Long, ParcelFileDescriptor>();
|
||||||
|
|
||||||
|
private long createKey(int id) {
|
||||||
|
int callingPid = Binder.getCallingPid();
|
||||||
|
return ((long) callingPid << 32) | ((long) id & 0xFFFFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final IOpenPgpService2.Stub mBinder = new IOpenPgpService2.Stub() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor createOutputPipe(int outputPipeId) {
|
||||||
|
try {
|
||||||
|
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||||
|
mOutputPipeMap.put(createKey(outputPipeId), pipe[1]);
|
||||||
|
return pipe[0];
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "IOException in OpenPgpService2", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent execute(Intent data, ParcelFileDescriptor input, int outputPipeId) {
|
||||||
|
long key = createKey(outputPipeId);
|
||||||
|
ParcelFileDescriptor output = mOutputPipeMap.get(key);
|
||||||
|
mOutputPipeMap.remove(key);
|
||||||
|
return executeInternal(data, input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -243,7 +243,7 @@ public class AppsListFragment extends ListFragment implements
|
|||||||
null,
|
null,
|
||||||
isInstalled(packageName),
|
isInstalled(packageName),
|
||||||
1, // registered!
|
1, // registered!
|
||||||
R.drawable.ic_launcher // icon is retrieved later
|
R.mipmap.ic_launcher // icon is retrieved later
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ public class AppsListFragment extends ListFragment implements
|
|||||||
name,
|
name,
|
||||||
isInstalled(packageName),
|
isInstalled(packageName),
|
||||||
1, // registered!
|
1, // registered!
|
||||||
R.drawable.ic_launcher // icon is retrieved later
|
R.mipmap.ic_launcher // icon is retrieved later
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,14 +40,9 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
|||||||
|
|
||||||
private static final int REQUEST_CODE_CREATE_KEY = 0x00008884;
|
private static final int REQUEST_CODE_CREATE_KEY = 0x00008884;
|
||||||
|
|
||||||
private Uri mAppUri;
|
|
||||||
private String mPreferredUserId;
|
private String mPreferredUserId;
|
||||||
private Intent mData;
|
private Intent mData;
|
||||||
|
|
||||||
private SelectSignKeyIdListFragment mListFragment;
|
|
||||||
private TextView mActionCreateKey;
|
|
||||||
private TextView mNone;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -62,15 +57,15 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mActionCreateKey = (TextView) findViewById(R.id.api_select_sign_key_create_key);
|
TextView createKeyButton = (TextView) findViewById(R.id.api_select_sign_key_create_key);
|
||||||
mActionCreateKey.setOnClickListener(new View.OnClickListener() {
|
createKeyButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
createKey(mPreferredUserId);
|
createKey(mPreferredUserId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mNone = (TextView) findViewById(R.id.api_select_sign_key_none);
|
TextView noneButton = (TextView) findViewById(R.id.api_select_sign_key_none);
|
||||||
mNone.setOnClickListener(new View.OnClickListener() {
|
noneButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// 0 is "none"
|
// 0 is "none"
|
||||||
@@ -82,16 +77,16 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
mAppUri = intent.getData();
|
Uri appUri = intent.getData();
|
||||||
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
|
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
|
||||||
mData = intent.getParcelableExtra(EXTRA_DATA);
|
mData = intent.getParcelableExtra(EXTRA_DATA);
|
||||||
if (mAppUri == null) {
|
if (appUri == null) {
|
||||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
Log.d(Constants.TAG, "uri: " + mAppUri);
|
Log.d(Constants.TAG, "uri: " + appUri);
|
||||||
startListFragments(savedInstanceState, mAppUri, mData);
|
startListFragments(savedInstanceState, appUri, mData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,11 +108,11 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance of the fragments
|
// Create an instance of the fragments
|
||||||
mListFragment = SelectSignKeyIdListFragment.newInstance(dataUri, data);
|
SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment.newInstance(dataUri, data);
|
||||||
// Add the fragment to the 'fragment_container' FrameLayout
|
// Add the fragment to the 'fragment_container' FrameLayout
|
||||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.api_select_sign_key_list_fragment, mListFragment)
|
.replace(R.id.api_select_sign_key_list_fragment, listFragment)
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
// do it immediately!
|
// do it immediately!
|
||||||
getSupportFragmentManager().executePendingTransactions();
|
getSupportFragmentManager().executePendingTransactions();
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import android.os.Parcelable;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||||
@@ -86,17 +89,10 @@ public class CertifyActionsParcel implements Parcelable {
|
|||||||
final public ArrayList<String> mUserIds;
|
final public ArrayList<String> mUserIds;
|
||||||
final public ArrayList<WrappedUserAttribute> mUserAttributes;
|
final public ArrayList<WrappedUserAttribute> mUserAttributes;
|
||||||
|
|
||||||
public CertifyAction(long masterKeyId, ArrayList<String> userIds) {
|
public CertifyAction(long masterKeyId, List<String> userIds, List<WrappedUserAttribute> attributes) {
|
||||||
mMasterKeyId = masterKeyId;
|
mMasterKeyId = masterKeyId;
|
||||||
mUserIds = userIds;
|
mUserIds = userIds == null ? null : new ArrayList<>(userIds);
|
||||||
mUserAttributes = null;
|
mUserAttributes = attributes == null ? null : new ArrayList<>(attributes);
|
||||||
}
|
|
||||||
|
|
||||||
public CertifyAction(long masterKeyId, ArrayList<String> userIds,
|
|
||||||
ArrayList<WrappedUserAttribute> attributes) {
|
|
||||||
mMasterKeyId = masterKeyId;
|
|
||||||
mUserIds = userIds;
|
|
||||||
mUserAttributes = attributes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class ContactSyncAdapterService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
|
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
|
||||||
final SyncResult syncResult) {
|
final SyncResult syncResult) {
|
||||||
Log.d(Constants.TAG, "Performing a sync!");
|
Log.d(Constants.TAG, "Performing a contact sync!");
|
||||||
// TODO: Import is currently disabled for 2.8, until we implement proper origin management
|
// TODO: Import is currently disabled for 2.8, until we implement proper origin management
|
||||||
// importDone.set(false);
|
// importDone.set(false);
|
||||||
// KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
|
// KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
|||||||
import org.sufficientlysecure.keychain.operations.RevokeOperation;
|
import org.sufficientlysecure.keychain.operations.RevokeOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||||
@@ -112,7 +112,7 @@ public class KeychainService extends Service implements Progressable {
|
|||||||
op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis),
|
op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis),
|
||||||
outerThis, mActionCanceled);
|
outerThis, mActionCanceled);
|
||||||
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
|
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
|
||||||
op = new PgpDecryptVerify(outerThis, new ProviderHelper(outerThis), outerThis);
|
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof SaveKeyringParcel) {
|
} else if (inputParcel instanceof SaveKeyringParcel) {
|
||||||
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
|
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
|
||||||
mActionCanceled);
|
mActionCanceled);
|
||||||
|
|||||||
@@ -0,0 +1,516 @@
|
|||||||
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.AbstractThreadedSyncAdapter;
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SyncResult;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||||
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class KeyserverSyncAdapterService extends Service {
|
||||||
|
|
||||||
|
// how often a sync should be initiated, in s
|
||||||
|
public static final long SYNC_INTERVAL =
|
||||||
|
Constants.DEBUG_KEYSERVER_SYNC
|
||||||
|
? TimeUnit.MINUTES.toSeconds(2) : TimeUnit.DAYS.toSeconds(3);
|
||||||
|
// time since last update after which a key should be updated again, in s
|
||||||
|
public static final long KEY_UPDATE_LIMIT =
|
||||||
|
Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7);
|
||||||
|
// time by which a sync is postponed in case of a
|
||||||
|
public static final long SYNC_POSTPONE_TIME =
|
||||||
|
Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5);
|
||||||
|
// Time taken by Orbot before a new circuit is created
|
||||||
|
public static final int ORBOT_CIRCUIT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(10);
|
||||||
|
|
||||||
|
|
||||||
|
private static final String ACTION_IGNORE_TOR = "ignore_tor";
|
||||||
|
private static final String ACTION_UPDATE_ALL = "update_all";
|
||||||
|
private static final String ACTION_SYNC_NOW = "sync_now";
|
||||||
|
private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync";
|
||||||
|
private static final String ACTION_START_ORBOT = "start_orbot";
|
||||||
|
private static final String ACTION_CANCEL = "cancel";
|
||||||
|
|
||||||
|
private AtomicBoolean mCancelled = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(final Intent intent, int flags, final int startId) {
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case ACTION_CANCEL: {
|
||||||
|
mCancelled.set(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting
|
||||||
|
// the sync directly from the notification is possible while the screen is on with
|
||||||
|
// UPDATE_ALL, but a postponed sync is only started if screen is off
|
||||||
|
case ACTION_SYNC_NOW: {
|
||||||
|
// this checks for screen on/off before sync, and postpones the sync if on
|
||||||
|
ContentResolver.requestSync(
|
||||||
|
new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE),
|
||||||
|
Constants.PROVIDER_AUTHORITY,
|
||||||
|
new Bundle()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_UPDATE_ALL: {
|
||||||
|
// does not check for screen on/off
|
||||||
|
asyncKeyUpdate(this, new CryptoInputParcel());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_IGNORE_TOR: {
|
||||||
|
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
|
||||||
|
asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_START_ORBOT: {
|
||||||
|
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
|
||||||
|
Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class);
|
||||||
|
startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true);
|
||||||
|
Messenger messenger = new Messenger(
|
||||||
|
new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: {
|
||||||
|
asyncKeyUpdate(KeyserverSyncAdapterService.this,
|
||||||
|
new CryptoInputParcel());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: {
|
||||||
|
asyncKeyUpdate(KeyserverSyncAdapterService.this,
|
||||||
|
new CryptoInputParcel(
|
||||||
|
ParcelableProxy.getForNoProxy()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: {
|
||||||
|
// just stop service
|
||||||
|
stopSelf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger);
|
||||||
|
startActivity(startOrbot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_DISMISS_NOTIFICATION: {
|
||||||
|
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
|
||||||
|
stopSelf(startId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class KeyserverSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
||||||
|
public KeyserverSyncAdapter() {
|
||||||
|
super(KeyserverSyncAdapterService.this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPerformSync(Account account, Bundle extras, String authority,
|
||||||
|
ContentProviderClient provider, SyncResult syncResult) {
|
||||||
|
Log.d(Constants.TAG, "Performing a keyserver sync!");
|
||||||
|
|
||||||
|
PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this
|
||||||
|
.getSystemService(Context.POWER_SERVICE);
|
||||||
|
@SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20
|
||||||
|
boolean isScreenOn = pm.isScreenOn();
|
||||||
|
|
||||||
|
if (!isScreenOn) {
|
||||||
|
Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this,
|
||||||
|
KeyserverSyncAdapterService.class);
|
||||||
|
serviceIntent.setAction(ACTION_UPDATE_ALL);
|
||||||
|
startService(serviceIntent);
|
||||||
|
} else {
|
||||||
|
postponeSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSyncCanceled() {
|
||||||
|
super.onSyncCanceled();
|
||||||
|
cancelUpdates(KeyserverSyncAdapterService.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return new KeyserverSyncAdapter().getSyncAdapterBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUpdateResult(ImportKeyResult result) {
|
||||||
|
if (result.isPending()) {
|
||||||
|
// result is pending due to Orbot not being started
|
||||||
|
// try to start it silently, if disabled show notifications
|
||||||
|
new OrbotHelper.SilentStartManager() {
|
||||||
|
@Override
|
||||||
|
protected void onOrbotStarted() {
|
||||||
|
// retry the update
|
||||||
|
asyncKeyUpdate(KeyserverSyncAdapterService.this,
|
||||||
|
new CryptoInputParcel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSilentStartDisabled() {
|
||||||
|
// show notification
|
||||||
|
NotificationManager manager =
|
||||||
|
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT,
|
||||||
|
getOrbotNoification(KeyserverSyncAdapterService.this));
|
||||||
|
}
|
||||||
|
}.startOrbotAndListen(this, false);
|
||||||
|
} else if (isUpdateCancelled()) {
|
||||||
|
Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME
|
||||||
|
+ "ms");
|
||||||
|
postponeSync();
|
||||||
|
} else {
|
||||||
|
Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys
|
||||||
|
+ " Failed: " + result.mBadKeys);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postponeSync() {
|
||||||
|
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
|
Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class);
|
||||||
|
serviceIntent.setAction(ACTION_SYNC_NOW);
|
||||||
|
PendingIntent pi = PendingIntent.getService(this, 0, serviceIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
alarmManager.set(
|
||||||
|
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
SystemClock.elapsedRealtime() + SYNC_POSTPONE_TIME,
|
||||||
|
pi
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void asyncKeyUpdate(final Context context,
|
||||||
|
final CryptoInputParcel cryptoInputParcel) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel);
|
||||||
|
handleUpdateResult(result);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized ImportKeyResult updateKeysFromKeyserver(final Context context,
|
||||||
|
final CryptoInputParcel cryptoInputParcel) {
|
||||||
|
mCancelled.set(false);
|
||||||
|
|
||||||
|
ArrayList<ParcelableKeyRing> keyList = getKeysToUpdate(context);
|
||||||
|
|
||||||
|
if (isUpdateCancelled()) { // if we've already been cancelled
|
||||||
|
return new ImportKeyResult(OperationResult.RESULT_CANCELLED,
|
||||||
|
new OperationResult.OperationLog());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cryptoInputParcel.getParcelableProxy() == null) {
|
||||||
|
// no explicit proxy, retrieve from preferences. Check if we should do a staggered sync
|
||||||
|
if (Preferences.getPreferences(context).getProxyPrefs().torEnabled) {
|
||||||
|
return staggeredUpdate(context, keyList, cryptoInputParcel);
|
||||||
|
} else {
|
||||||
|
return directUpdate(context, keyList, cryptoInputParcel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return directUpdate(context, keyList, cryptoInputParcel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportKeyResult directUpdate(Context context, ArrayList<ParcelableKeyRing> keyList,
|
||||||
|
CryptoInputParcel cryptoInputParcel) {
|
||||||
|
Log.d(Constants.TAG, "Starting normal update");
|
||||||
|
ImportOperation importOp = new ImportOperation(context, new ProviderHelper(context), null);
|
||||||
|
return importOp.execute(
|
||||||
|
new ImportKeyringParcel(keyList,
|
||||||
|
Preferences.getPreferences(context).getPreferredKeyserver()),
|
||||||
|
cryptoInputParcel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* will perform a staggered update of user's keys using delays to ensure new Tor circuits, as
|
||||||
|
* performed by parcimonie. Relevant issue and method at:
|
||||||
|
* https://github.com/open-keychain/open-keychain/issues/1337
|
||||||
|
*
|
||||||
|
* @return result of the sync
|
||||||
|
*/
|
||||||
|
private ImportKeyResult staggeredUpdate(Context context, ArrayList<ParcelableKeyRing> keyList,
|
||||||
|
CryptoInputParcel cryptoInputParcel) {
|
||||||
|
Log.d(Constants.TAG, "Starting staggered update");
|
||||||
|
// final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
|
||||||
|
final int WEEK_IN_SECONDS = 0;
|
||||||
|
ImportOperation.KeyImportAccumulator accumulator
|
||||||
|
= new ImportOperation.KeyImportAccumulator(keyList.size(), null);
|
||||||
|
for (ParcelableKeyRing keyRing : keyList) {
|
||||||
|
int waitTime;
|
||||||
|
int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size()));
|
||||||
|
if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT) {
|
||||||
|
waitTime = staggeredTime;
|
||||||
|
} else {
|
||||||
|
waitTime = ORBOT_CIRCUIT_TIMEOUT + new Random().nextInt(ORBOT_CIRCUIT_TIMEOUT);
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint +
|
||||||
|
" with a wait time of " + waitTime + "s");
|
||||||
|
try {
|
||||||
|
Thread.sleep(waitTime * 1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(Constants.TAG, "Exception during sleep between key updates", e);
|
||||||
|
// skip this one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ArrayList<ParcelableKeyRing> keyWrapper = new ArrayList<>();
|
||||||
|
keyWrapper.add(keyRing);
|
||||||
|
if (isUpdateCancelled()) {
|
||||||
|
return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED,
|
||||||
|
new OperationResult.OperationLog());
|
||||||
|
}
|
||||||
|
ImportKeyResult result =
|
||||||
|
new ImportOperation(context, new ProviderHelper(context), null, mCancelled)
|
||||||
|
.execute(
|
||||||
|
new ImportKeyringParcel(
|
||||||
|
keyWrapper,
|
||||||
|
Preferences.getPreferences(context)
|
||||||
|
.getPreferredKeyserver()
|
||||||
|
),
|
||||||
|
cryptoInputParcel
|
||||||
|
);
|
||||||
|
if (result.isPending()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
accumulator.accumulateKeyImport(result);
|
||||||
|
}
|
||||||
|
return accumulator.getConsolidatedResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Get keys which have been updated recently and therefore do not need to
|
||||||
|
* be updated now
|
||||||
|
* 2. Get list of all keys and filter out ones that don't need to be updated
|
||||||
|
* 3. Return keys to be updated
|
||||||
|
*
|
||||||
|
* @return list of keys that require update
|
||||||
|
*/
|
||||||
|
private ArrayList<ParcelableKeyRing> getKeysToUpdate(Context context) {
|
||||||
|
|
||||||
|
// 1. Get keys which have been updated recently and don't need to updated now
|
||||||
|
final int INDEX_UPDATED_KEYS_MASTER_KEY_ID = 0;
|
||||||
|
final int INDEX_LAST_UPDATED = 1;
|
||||||
|
|
||||||
|
// all time in seconds not milliseconds
|
||||||
|
final long CURRENT_TIME = GregorianCalendar.getInstance().getTimeInMillis() / 1000;
|
||||||
|
Cursor updatedKeysCursor = context.getContentResolver().query(
|
||||||
|
KeychainContract.UpdatedKeys.CONTENT_URI,
|
||||||
|
new String[]{
|
||||||
|
KeychainContract.UpdatedKeys.MASTER_KEY_ID,
|
||||||
|
KeychainContract.UpdatedKeys.LAST_UPDATED
|
||||||
|
},
|
||||||
|
"? - " + KeychainContract.UpdatedKeys.LAST_UPDATED + " < " + KEY_UPDATE_LIMIT,
|
||||||
|
new String[]{"" + CURRENT_TIME},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
ArrayList<Long> ignoreMasterKeyIds = new ArrayList<>();
|
||||||
|
while (updatedKeysCursor.moveToNext()) {
|
||||||
|
long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID);
|
||||||
|
Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {"
|
||||||
|
+ updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s");
|
||||||
|
ignoreMasterKeyIds.add(masterKeyId);
|
||||||
|
}
|
||||||
|
updatedKeysCursor.close();
|
||||||
|
|
||||||
|
// 2. Make a list of public keys which should be updated
|
||||||
|
final int INDEX_MASTER_KEY_ID = 0;
|
||||||
|
final int INDEX_FINGERPRINT = 1;
|
||||||
|
Cursor keyCursor = context.getContentResolver().query(
|
||||||
|
KeychainContract.KeyRings.buildUnifiedKeyRingsUri(),
|
||||||
|
new String[]{
|
||||||
|
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||||
|
KeychainContract.KeyRings.FINGERPRINT
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (keyCursor == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||||
|
while (keyCursor.moveToNext()) {
|
||||||
|
long keyId = keyCursor.getLong(INDEX_MASTER_KEY_ID);
|
||||||
|
if (ignoreMasterKeyIds.contains(keyId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "Keyserver sync: Updating {" + keyId + "}");
|
||||||
|
String fingerprint = KeyFormattingUtils
|
||||||
|
.convertFingerprintToHex(keyCursor.getBlob(INDEX_FINGERPRINT));
|
||||||
|
String hexKeyId = KeyFormattingUtils
|
||||||
|
.convertKeyIdToHex(keyId);
|
||||||
|
// we aren't updating from keybase as of now
|
||||||
|
keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId, null));
|
||||||
|
}
|
||||||
|
keyCursor.close();
|
||||||
|
|
||||||
|
return keyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUpdateCancelled() {
|
||||||
|
return mCancelled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* will cancel an update already in progress. We send an Intent to cancel it instead of simply
|
||||||
|
* modifying a static variable sync the service is running in a process that is different from
|
||||||
|
* the default application process where the UI code runs.
|
||||||
|
*
|
||||||
|
* @param context used to send an Intent to the service requesting cancellation.
|
||||||
|
*/
|
||||||
|
public static void cancelUpdates(Context context) {
|
||||||
|
Intent intent = new Intent(context, KeyserverSyncAdapterService.class);
|
||||||
|
intent.setAction(ACTION_CANCEL);
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification getOrbotNoification(Context context) {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
|
||||||
|
builder.setSmallIcon(R.drawable.ic_stat_notify_24dp)
|
||||||
|
.setLargeIcon(getBitmap(R.mipmap.ic_launcher, context))
|
||||||
|
.setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title))
|
||||||
|
.setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg))
|
||||||
|
.setAutoCancel(true);
|
||||||
|
|
||||||
|
// In case the user decides to not use tor
|
||||||
|
Intent ignoreTorIntent = new Intent(context, KeyserverSyncAdapterService.class);
|
||||||
|
ignoreTorIntent.setAction(ACTION_IGNORE_TOR);
|
||||||
|
PendingIntent ignoreTorPi = PendingIntent.getService(
|
||||||
|
context,
|
||||||
|
0, // security not issue since we're giving this pending intent to Notification Manager
|
||||||
|
ignoreTorIntent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.addAction(R.drawable.ic_stat_tor_off,
|
||||||
|
context.getString(R.string.keyserver_sync_orbot_notif_ignore),
|
||||||
|
ignoreTorPi);
|
||||||
|
|
||||||
|
Intent startOrbotIntent = new Intent(context, KeyserverSyncAdapterService.class);
|
||||||
|
startOrbotIntent.setAction(ACTION_START_ORBOT);
|
||||||
|
PendingIntent startOrbotPi = PendingIntent.getService(
|
||||||
|
context,
|
||||||
|
0, // security not issue since we're giving this pending intent to Notification Manager
|
||||||
|
startOrbotIntent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.addAction(R.drawable.ic_stat_tor,
|
||||||
|
context.getString(R.string.keyserver_sync_orbot_notif_start),
|
||||||
|
startOrbotPi
|
||||||
|
);
|
||||||
|
builder.setContentIntent(startOrbotPi);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enableKeyserverSync(Context context) {
|
||||||
|
try {
|
||||||
|
AccountManager manager = AccountManager.get(context);
|
||||||
|
Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE);
|
||||||
|
|
||||||
|
Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
|
||||||
|
if (accounts.length == 0) {
|
||||||
|
if (!manager.addAccountExplicitly(account, null, null)) {
|
||||||
|
Log.e(Constants.TAG, "Adding account failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for keyserver sync
|
||||||
|
ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1);
|
||||||
|
ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY,
|
||||||
|
true);
|
||||||
|
ContentResolver.addPeriodicSync(
|
||||||
|
account,
|
||||||
|
Constants.PROVIDER_AUTHORITY,
|
||||||
|
new Bundle(),
|
||||||
|
SYNC_INTERVAL
|
||||||
|
);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
Log.e(Constants.TAG, "SecurityException when adding the account", e);
|
||||||
|
Toast.makeText(context, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
|
||||||
|
private Bitmap getBitmap(int resId, Context context) {
|
||||||
|
int mLargeIconWidth = (int) context.getResources().getDimension(
|
||||||
|
android.R.dimen.notification_large_icon_width);
|
||||||
|
int mLargeIconHeight = (int) context.getResources().getDimension(
|
||||||
|
android.R.dimen.notification_large_icon_height);
|
||||||
|
Drawable d;
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// noinspection deprecation (can't help it at this api level)
|
||||||
|
d = context.getResources().getDrawable(resId);
|
||||||
|
} else {
|
||||||
|
d = context.getDrawable(resId);
|
||||||
|
}
|
||||||
|
if (d == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas c = new Canvas(b);
|
||||||
|
d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight);
|
||||||
|
d.draw(c);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,8 +101,6 @@ public class PassphraseCacheService extends Service {
|
|||||||
|
|
||||||
private static final long DEFAULT_TTL = 15;
|
private static final long DEFAULT_TTL = 15;
|
||||||
|
|
||||||
private static final int NOTIFICATION_ID = 1;
|
|
||||||
|
|
||||||
private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1;
|
private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1;
|
||||||
private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND = 2;
|
private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND = 2;
|
||||||
|
|
||||||
@@ -477,7 +475,7 @@ public class PassphraseCacheService extends Service {
|
|||||||
|
|
||||||
private void updateService() {
|
private void updateService() {
|
||||||
if (mPassphraseCache.size() > 0) {
|
if (mPassphraseCache.size() > 0) {
|
||||||
startForeground(NOTIFICATION_ID, getNotification());
|
startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification());
|
||||||
} else {
|
} else {
|
||||||
// stop whole service if no cached passphrases remaining
|
// stop whole service if no cached passphrases remaining
|
||||||
Log.d(Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
|
Log.d(Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
|
||||||
@@ -511,7 +509,7 @@ public class PassphraseCacheService extends Service {
|
|||||||
private Notification getNotification() {
|
private Notification getNotification() {
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||||
builder.setSmallIcon(R.drawable.ic_stat_notify_24dp)
|
builder.setSmallIcon(R.drawable.ic_stat_notify_24dp)
|
||||||
.setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext()))
|
.setLargeIcon(getBitmap(R.mipmap.ic_launcher, getBaseContext()))
|
||||||
.setContentTitle(getResources().getQuantityString(R.plurals.passp_cache_notif_n_keys,
|
.setContentTitle(getResources().getQuantityString(R.plurals.passp_cache_notif_n_keys,
|
||||||
mPassphraseCache.size(), mPassphraseCache.size()))
|
mPassphraseCache.size(), mPassphraseCache.size()))
|
||||||
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
||||||
@@ -603,4 +601,4 @@ public class PassphraseCacheService extends Service {
|
|||||||
this.passphrase = passphrase;
|
this.passphrase = passphrase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@@ -47,7 +51,20 @@ public class BackupFragment extends Fragment {
|
|||||||
private int mIndex;
|
private int mIndex;
|
||||||
|
|
||||||
static final int REQUEST_REPEAT_PASSPHRASE = 1;
|
static final int REQUEST_REPEAT_PASSPHRASE = 1;
|
||||||
|
private ExportHelper mExportHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
// we won't get attached to a non-fragment activity, so the cast should be safe
|
||||||
|
mExportHelper = new ExportHelper((FragmentActivity) activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetach() {
|
||||||
|
super.onDetach();
|
||||||
|
mExportHelper = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
@@ -80,8 +97,7 @@ public class BackupFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!includeSecretKeys) {
|
if (!includeSecretKeys) {
|
||||||
ExportHelper exportHelper = new ExportHelper(activity);
|
startBackup(false);
|
||||||
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,8 +152,7 @@ public class BackupFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExportHelper exportHelper = new ExportHelper(activity);
|
startBackup(true);
|
||||||
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}.execute(activity.getContentResolver());
|
}.execute(activity.getContentResolver());
|
||||||
@@ -167,8 +182,19 @@ public class BackupFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExportHelper exportHelper = new ExportHelper(getActivity());
|
startBackup(true);
|
||||||
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startBackup(boolean exportSecret) {
|
||||||
|
File filename;
|
||||||
|
String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||||
|
if (exportSecret) {
|
||||||
|
filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
|
||||||
|
} else {
|
||||||
|
filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
|
||||||
|
}
|
||||||
|
mExportHelper.showExportKeysDialog(null, filename, exportSecret);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
|||||||
|
|
||||||
protected Uri mDataUri;
|
protected Uri mDataUri;
|
||||||
|
|
||||||
|
public static final String EXTRA_ENABLE_WORD_CONFIRM = "enable_word_confirm";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -40,6 +42,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
|||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
boolean enableWordConfirm = getIntent().getBooleanExtra(EXTRA_ENABLE_WORD_CONFIRM, false);
|
||||||
|
|
||||||
setFullScreenDialogClose(new View.OnClickListener() {
|
setFullScreenDialogClose(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -50,7 +53,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
|||||||
|
|
||||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||||
|
|
||||||
startFragment(savedInstanceState, mDataUri);
|
startFragment(savedInstanceState, mDataUri, enableWordConfirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,7 +61,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
|||||||
setContentView(R.layout.certify_fingerprint_activity);
|
setContentView(R.layout.certify_fingerprint_activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFragment(Bundle savedInstanceState, Uri dataUri) {
|
private void startFragment(Bundle savedInstanceState, Uri dataUri, boolean enableWordConfirm) {
|
||||||
// However, if we're being restored from a previous state,
|
// However, if we're being restored from a previous state,
|
||||||
// then we don't need to do anything and should return or else
|
// then we don't need to do anything and should return or else
|
||||||
// we could end up with overlapping fragments.
|
// we could end up with overlapping fragments.
|
||||||
@@ -67,7 +70,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance of the fragment
|
// Create an instance of the fragment
|
||||||
CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri);
|
CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri, enableWordConfirm);
|
||||||
|
|
||||||
// Add the fragment to the 'fragment_container' FrameLayout
|
// Add the fragment to the 'fragment_container' FrameLayout
|
||||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
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.ui.util.ExperimentalWordConfirm;
|
||||||
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;
|
||||||
|
|
||||||
@@ -44,23 +46,24 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
|||||||
static final int REQUEST_CERTIFY = 1;
|
static final int REQUEST_CERTIFY = 1;
|
||||||
|
|
||||||
public static final String ARG_DATA_URI = "uri";
|
public static final String ARG_DATA_URI = "uri";
|
||||||
|
public static final String ARG_ENABLE_WORD_CONFIRM = "enable_word_confirm";
|
||||||
|
|
||||||
private TextView mFingerprint;
|
private TextView mFingerprint;
|
||||||
|
private TextView mIntro;
|
||||||
|
|
||||||
private static final int LOADER_ID_UNIFIED = 0;
|
private static final int LOADER_ID_UNIFIED = 0;
|
||||||
|
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
private boolean mEnableWordConfirm;
|
||||||
private View mActionNo;
|
|
||||||
private View mActionYes;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
public static CertifyFingerprintFragment newInstance(Uri dataUri) {
|
public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enableWordConfirm) {
|
||||||
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
|
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||||
|
args.putBoolean(ARG_ENABLE_WORD_CONFIRM, enableWordConfirm);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
@@ -72,18 +75,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
|||||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||||
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
|
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
|
||||||
|
|
||||||
mActionNo = view.findViewById(R.id.certify_fingerprint_button_no);
|
View actionNo = view.findViewById(R.id.certify_fingerprint_button_no);
|
||||||
mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
|
View actionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
|
||||||
|
|
||||||
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
|
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
|
||||||
|
mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro);
|
||||||
|
|
||||||
mActionNo.setOnClickListener(new View.OnClickListener() {
|
actionNo.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mActionYes.setOnClickListener(new View.OnClickListener() {
|
actionYes.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
certify(mDataUri);
|
certify(mDataUri);
|
||||||
@@ -103,6 +107,11 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
|||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mEnableWordConfirm = getArguments().getBoolean(ARG_ENABLE_WORD_CONFIRM);
|
||||||
|
|
||||||
|
if (mEnableWordConfirm) {
|
||||||
|
mIntro.setText(R.string.certify_fingerprint_text_words);
|
||||||
|
}
|
||||||
|
|
||||||
loadData(dataUri);
|
loadData(dataUri);
|
||||||
}
|
}
|
||||||
@@ -149,10 +158,13 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
|||||||
switch (loader.getId()) {
|
switch (loader.getId()) {
|
||||||
case LOADER_ID_UNIFIED: {
|
case LOADER_ID_UNIFIED: {
|
||||||
if (data.moveToFirst()) {
|
if (data.moveToFirst()) {
|
||||||
|
|
||||||
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
|
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
|
||||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
|
|
||||||
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
|
if (mEnableWordConfirm) {
|
||||||
|
displayWordConfirm(fingerprintBlob);
|
||||||
|
} else {
|
||||||
|
displayHexConfirm(fingerprintBlob);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -162,6 +174,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
|||||||
setContentShown(true);
|
setContentShown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayHexConfirm(byte[] fingerprintBlob) {
|
||||||
|
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
|
||||||
|
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayWordConfirm(byte[] fingerprintBlob) {
|
||||||
|
String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob);
|
||||||
|
|
||||||
|
mFingerprint.setTextSize(24);
|
||||||
|
mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
|
||||||
|
mFingerprint.setText(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
||||||
* We need to make sure we are no longer using it.
|
* We need to make sure we are no longer using it.
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ public class CertifyKeyFragment
|
|||||||
};
|
};
|
||||||
private static final int INDEX_MASTER_KEY_ID = 1;
|
private static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
private static final int INDEX_USER_ID = 2;
|
private static final int INDEX_USER_ID = 2;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private static final int INDEX_IS_PRIMARY = 3;
|
private static final int INDEX_IS_PRIMARY = 3;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private static final int INDEX_IS_REVOKED = 4;
|
private static final int INDEX_IS_REVOKED = 4;
|
||||||
|
|
||||||
private MultiUserIdsAdapter mUserIdsAdapter;
|
private MultiUserIdsAdapter mUserIdsAdapter;
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ import android.content.Context;
|
|||||||
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.annotation.Nullable;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
|
|
||||||
@@ -93,7 +94,9 @@ public class DecryptActivity extends BaseActivity {
|
|||||||
} else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
} else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
||||||
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
Uri uri = readToTempFile(text);
|
Uri uri = readToTempFile(text);
|
||||||
uris.add(uri);
|
if (uri != null) {
|
||||||
|
uris.add(uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -105,7 +108,9 @@ public class DecryptActivity extends BaseActivity {
|
|||||||
} else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
} else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
||||||
for (String text : intent.getStringArrayListExtra(Intent.EXTRA_TEXT)) {
|
for (String text : intent.getStringArrayListExtra(Intent.EXTRA_TEXT)) {
|
||||||
Uri uri = readToTempFile(text);
|
Uri uri = readToTempFile(text);
|
||||||
uris.add(uri);
|
if (uri != null) {
|
||||||
|
uris.add(uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +144,9 @@ public class DecryptActivity extends BaseActivity {
|
|||||||
String text = clip.getItemAt(0).coerceToText(this).toString();
|
String text = clip.getItemAt(0).coerceToText(this).toString();
|
||||||
uri = readToTempFile(text);
|
uri = readToTempFile(text);
|
||||||
}
|
}
|
||||||
uris.add(uri);
|
if (uri != null) {
|
||||||
|
uris.add(uri);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -170,9 +177,17 @@ public class DecryptActivity extends BaseActivity {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri readToTempFile(String text) throws IOException {
|
@Nullable public Uri readToTempFile(String text) throws IOException {
|
||||||
Uri tempFile = TemporaryStorageProvider.createFile(this);
|
Uri tempFile = TemporaryStorageProvider.createFile(this);
|
||||||
OutputStream outStream = getContentResolver().openOutputStream(tempFile);
|
OutputStream outStream = getContentResolver().openOutputStream(tempFile);
|
||||||
|
|
||||||
|
// clean up ascii armored message, fixing newlines and stuff
|
||||||
|
String cleanedText = PgpHelper.getPgpContent(text);
|
||||||
|
if (cleanedText == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if cleanup didn't work, just try the raw data
|
||||||
outStream.write(text.getBytes());
|
outStream.write(text.getBytes());
|
||||||
outStream.close();
|
outStream.close();
|
||||||
return tempFile;
|
return tempFile;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import android.graphics.drawable.BitmapDrawable;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.DefaultItemAnimator;
|
import android.support.v7.widget.DefaultItemAnimator;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
@@ -222,18 +221,6 @@ public class DecryptListFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
|
||||||
File file = new File(inputUri.getPath());
|
|
||||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
|
||||||
File targetFile = new File(parentDir, originalFilename);
|
|
||||||
FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
|
|
||||||
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
|
||||||
} else {
|
|
||||||
FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
@@ -388,7 +375,7 @@ public class DecryptListFragment
|
|||||||
onFileClick = new OnClickListener() {
|
onFileClick = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
displayWithViewIntent(uri);
|
displayWithViewIntent(uri, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -413,7 +400,7 @@ public class DecryptListFragment
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void displayWithViewIntent(final Uri uri) {
|
public void displayWithViewIntent(final Uri uri, boolean share) {
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
if (activity == null || mCurrentInputUri != null) {
|
if (activity == null || mCurrentInputUri != null) {
|
||||||
return;
|
return;
|
||||||
@@ -432,51 +419,40 @@ public class DecryptListFragment
|
|||||||
// OpenKeychain's internal viewer
|
// OpenKeychain's internal viewer
|
||||||
if ("text/plain".equals(metadata.getMimeType())) {
|
if ("text/plain".equals(metadata.getMimeType())) {
|
||||||
|
|
||||||
parseMime(outputUri);
|
if (share) {
|
||||||
|
try {
|
||||||
|
String plaintext = FileHelper.readTextFromUri(activity, outputUri, result.getCharset());
|
||||||
|
|
||||||
// this is a significant i/o operation, use an asynctask
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
// new AsyncTask<Void,Void,Intent>() {
|
intent.setType(metadata.getMimeType());
|
||||||
//
|
intent.putExtra(Intent.EXTRA_TEXT, plaintext);
|
||||||
// @Override
|
startActivity(intent);
|
||||||
// protected Intent doInBackground(Void... params) {
|
|
||||||
//
|
} catch (IOException e) {
|
||||||
// Activity activity = getActivity();
|
Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show();
|
||||||
// if (activity == null) {
|
}
|
||||||
// return null;
|
|
||||||
// }
|
return;
|
||||||
//
|
}
|
||||||
// Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
// intent.setDataAndType(outputUri, "text/plain");
|
Intent intent = new Intent(activity, DisplayTextActivity.class);
|
||||||
// intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
// return intent;
|
intent.setDataAndType(outputUri, metadata.getMimeType());
|
||||||
// }
|
intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result);
|
||||||
//
|
activity.startActivity(intent);
|
||||||
// @Override
|
|
||||||
// protected void onPostExecute(Intent intent) {
|
|
||||||
// // for result so we can possibly get a snackbar error from internal viewer
|
|
||||||
// Activity activity = getActivity();
|
|
||||||
// if (intent == null || activity == null) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// LabeledIntent internalIntent = new LabeledIntent(
|
|
||||||
// new Intent(intent)
|
|
||||||
// .setClass(activity, DisplayTextActivity.class)
|
|
||||||
// .putExtra(DisplayTextActivity.EXTRA_METADATA, result),
|
|
||||||
// BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher);
|
|
||||||
//
|
|
||||||
// Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
|
|
||||||
// chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
|
|
||||||
// new Parcelable[] { internalIntent });
|
|
||||||
//
|
|
||||||
// activity.startActivity(chooserIntent);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }.execute();
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
||||||
intent.setDataAndType(outputUri, metadata.getMimeType());
|
Intent intent;
|
||||||
|
if (share) {
|
||||||
|
intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
intent.setType(metadata.getMimeType());
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, outputUri);
|
||||||
|
} else {
|
||||||
|
intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setDataAndType(outputUri, metadata.getMimeType());
|
||||||
|
}
|
||||||
|
|
||||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
|
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
|
||||||
@@ -486,52 +462,6 @@ public class DecryptListFragment
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseMime(final Uri inputUri) {
|
|
||||||
|
|
||||||
CryptoOperationHelper.Callback<MimeParsingParcel, MimeParsingResult> callback
|
|
||||||
= new CryptoOperationHelper.Callback<MimeParsingParcel, MimeParsingResult>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MimeParsingParcel createOperationInput() {
|
|
||||||
return new MimeParsingParcel(inputUri, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCryptoOperationSuccess(MimeParsingResult result) {
|
|
||||||
handleResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCryptoOperationCancelled() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCryptoOperationError(MimeParsingResult result) {
|
|
||||||
handleResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleResult(MimeParsingResult result) {
|
|
||||||
// TODO: merge with other log
|
|
||||||
// saveKeyResult.getLog().add(result, 0);
|
|
||||||
|
|
||||||
mOutputUris = new HashMap<>(result.getTemporaryUris().size());
|
|
||||||
for (Uri tempUri : result.getTemporaryUris()) {
|
|
||||||
// TODO: use same inputUri for all?
|
|
||||||
mOutputUris.put(inputUri, tempUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CryptoOperationHelper mimeParsingHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading);
|
|
||||||
mimeParsingHelper.cryptoOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PgpDecryptVerifyInputParcel createOperationInput() {
|
public PgpDecryptVerifyInputParcel createOperationInput() {
|
||||||
|
|
||||||
@@ -576,13 +506,17 @@ public class DecryptListFragment
|
|||||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result);
|
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result);
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.decrypt_share:
|
||||||
|
displayWithViewIntent(model.mInputUri, true);
|
||||||
|
return true;
|
||||||
case R.id.decrypt_save:
|
case R.id.decrypt_save:
|
||||||
OpenPgpMetadata metadata = result.getDecryptionMetadata();
|
OpenPgpMetadata metadata = result.getDecryptionMetadata();
|
||||||
if (metadata == null) {
|
if (metadata == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
mCurrentInputUri = model.mInputUri;
|
mCurrentInputUri = model.mInputUri;
|
||||||
askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType());
|
FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(),
|
||||||
|
R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
|
||||||
return true;
|
return true;
|
||||||
case R.id.decrypt_delete:
|
case R.id.decrypt_delete:
|
||||||
deleteFile(activity, model.mInputUri);
|
deleteFile(activity, model.mInputUri);
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
|
|||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|||||||
case LOADER_ID_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
||||||
return new CursorLoader(getActivity(), baseUri,
|
return new CursorLoader(getActivity(), baseUri,
|
||||||
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
|
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
case LOADER_ID_SUBKEYS: {
|
case LOADER_ID_SUBKEYS: {
|
||||||
|
|||||||
@@ -83,11 +83,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
|
|||||||
mDecryptFile.setOnClickListener(new View.OnClickListener() {
|
mDecryptFile.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
FileHelper.openDocument(EncryptDecryptOverviewFragment.this, null, "*/*", false, REQUEST_CODE_INPUT);
|
||||||
FileHelper.openDocument(EncryptDecryptOverviewFragment.this, "*/*", REQUEST_CODE_INPUT);
|
|
||||||
} else {
|
|
||||||
FileHelper.openFile(EncryptDecryptOverviewFragment.this, null, "*/*", REQUEST_CODE_INPUT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -196,13 +195,9 @@ public class EncryptFilesFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addInputUri() {
|
private void addInputUri() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
FileHelper.openDocument(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
|
||||||
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
|
null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri,
|
||||||
} else {
|
"*/*", true, REQUEST_CODE_INPUT);
|
||||||
FileHelper.openFile(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
|
|
||||||
null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri,
|
|
||||||
"*/*", REQUEST_CODE_INPUT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInputUri(Uri inputUri) {
|
private void addInputUri(Uri inputUri) {
|
||||||
@@ -230,19 +225,8 @@ public class EncryptFilesFragment
|
|||||||
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
|
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||||
Uri inputUri = model.inputUri;
|
Uri inputUri = model.inputUri;
|
||||||
saveDocumentIntent(targetName, inputUri);
|
FileHelper.saveDocument(this, targetName, inputUri,
|
||||||
}
|
R.string.title_encrypt_to_file, R.string.specify_file_to_encrypt_to, REQUEST_CODE_OUTPUT);
|
||||||
|
|
||||||
private void saveDocumentIntent(String targetName, Uri inputUri) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
|
||||||
File file = new File(inputUri.getPath());
|
|
||||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
|
||||||
File targetFile = new File(parentDir, targetName);
|
|
||||||
FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file),
|
|
||||||
getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
|
||||||
} else {
|
|
||||||
FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFile(Intent data) {
|
public void addFile(Intent data) {
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ public class ImportKeysFileFragment extends Fragment {
|
|||||||
// open .asc or .gpg files
|
// open .asc or .gpg files
|
||||||
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
|
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
|
||||||
// or gpg types!
|
// or gpg types!
|
||||||
FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR),
|
FileHelper.openDocument(ImportKeysFileFragment.this,
|
||||||
"*/*", REQUEST_CODE_FILE);
|
Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,6 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -70,16 +65,19 @@ import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
|
|||||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
|
||||||
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.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
|
||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
|
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
|
||||||
* StickyListHeaders library which does not extend upon ListView.
|
* StickyListHeaders library which does not extend upon ListView.
|
||||||
@@ -536,7 +534,7 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
Notify.create(activity, R.string.error_loading_keys, Style.ERROR);
|
Notify.create(activity, R.string.error_loading_keys, Notify.Style.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,12 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v4.app.ListFragment;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -37,19 +38,19 @@ import android.widget.ArrayAdapter;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
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.operations.results.OperationResult.LogEntryParcel;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
|
||||||
|
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
public class LogDisplayFragment extends ListFragment implements OnItemClickListener {
|
public class LogDisplayFragment extends ListFragment implements OnItemClickListener {
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
|||||||
public static final String EXTRA_RESULT = "log";
|
public static final String EXTRA_RESULT = "log";
|
||||||
protected int mTextColor;
|
protected int mTextColor;
|
||||||
|
|
||||||
|
private Uri mLogTempFile;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -118,170 +121,40 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_log_display_export_log:
|
case R.id.menu_log_display_export_log:
|
||||||
exportLog();
|
shareLog();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportLog() {
|
private void shareLog() {
|
||||||
showExportLogDialog(new File(Constants.Path.APP_DIR, "export.log"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeToLogFile(final OperationResult.OperationLog operationLog, final File f) {
|
Activity activity = getActivity();
|
||||||
OperationResult.OperationLog currLog = new OperationResult.OperationLog();
|
if (activity == null) {
|
||||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG, 0);
|
return;
|
||||||
|
|
||||||
boolean error = false;
|
|
||||||
|
|
||||||
PrintWriter pw = null;
|
|
||||||
try {
|
|
||||||
pw = new PrintWriter(f);
|
|
||||||
pw.print(getPrintableOperationLog(operationLog, ""));
|
|
||||||
if (pw.checkError()) {//IOException
|
|
||||||
Log.e(Constants.TAG, "Log Export I/O Exception " + f.getAbsolutePath());
|
|
||||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1);
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Log.e(Constants.TAG, "File not found for exporting log " + f.getAbsolutePath());
|
|
||||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN, 1);
|
|
||||||
error = true;
|
|
||||||
}
|
}
|
||||||
if (pw != null) {
|
|
||||||
pw.close();
|
String log = mResult.getLog().getPrintableOperationLog(getResources(), 0);
|
||||||
if (!error && pw.checkError()) {//check if it is only pw.close() which generated error
|
|
||||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1);
|
// if there is no log temp file yet, create one
|
||||||
error = true;
|
if (mLogTempFile == null) {
|
||||||
|
mLogTempFile = TemporaryStorageProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain");
|
||||||
|
try {
|
||||||
|
OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile);
|
||||||
|
outputStream.write(log.getBytes());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Notify.create(activity, R.string.error_log_share_internal, Style.ERROR).show();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!error) {
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_SUCCESS, 1);
|
intent.putExtra(Intent.EXTRA_STREAM, mLogTempFile);
|
||||||
}
|
intent.setType("text/plain");
|
||||||
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
startActivity(intent);
|
||||||
|
|
||||||
int opResultCode = error ? OperationResult.RESULT_ERROR : OperationResult.RESULT_OK;
|
|
||||||
OperationResult opResult = new LogExportResult(opResultCode, currLog);
|
|
||||||
opResult.createNotify(getActivity()).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns an indented String of an entire OperationLog
|
|
||||||
*
|
|
||||||
* @param opLog log to be converted to indented, printable format
|
|
||||||
* @param basePadding padding to add at the start of all log entries, made for use with SubLogs
|
|
||||||
* @return printable, indented version of passed operationLog
|
|
||||||
*/
|
|
||||||
private String getPrintableOperationLog(OperationResult.OperationLog opLog, String basePadding) {
|
|
||||||
String log = "";
|
|
||||||
for (LogEntryParcel anOpLog : opLog) {
|
|
||||||
log += getPrintableLogEntry(anOpLog, basePadding) + "\n";
|
|
||||||
}
|
|
||||||
log = log.substring(0, log.length() - 1);//gets rid of extra new line
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns an indented String of a LogEntryParcel including any sub-logs it may contain
|
|
||||||
*
|
|
||||||
* @param entryParcel log entryParcel whose String representation is to be obtained
|
|
||||||
* @return indented version of passed log entryParcel in a readable format
|
|
||||||
*/
|
|
||||||
private String getPrintableLogEntry(OperationResult.LogEntryParcel entryParcel,
|
|
||||||
String basePadding) {
|
|
||||||
|
|
||||||
final String indent = " ";//4 spaces = 1 Indent level
|
|
||||||
|
|
||||||
String padding = basePadding;
|
|
||||||
for (int i = 0; i < entryParcel.mIndent; i++) {
|
|
||||||
padding += indent;
|
|
||||||
}
|
|
||||||
String logText = padding;
|
|
||||||
|
|
||||||
switch (entryParcel.mType.mLevel) {
|
|
||||||
case DEBUG:
|
|
||||||
logText += "[DEBUG]";
|
|
||||||
break;
|
|
||||||
case INFO:
|
|
||||||
logText += "[INFO]";
|
|
||||||
break;
|
|
||||||
case WARN:
|
|
||||||
logText += "[WARN]";
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
logText += "[ERROR]";
|
|
||||||
break;
|
|
||||||
case START:
|
|
||||||
logText += "[START]";
|
|
||||||
break;
|
|
||||||
case OK:
|
|
||||||
logText += "[OK]";
|
|
||||||
break;
|
|
||||||
case CANCELLED:
|
|
||||||
logText += "[CANCELLED]";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// special case: first parameter may be a quantity
|
|
||||||
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
|
|
||||||
&& entryParcel.mParameters[0] instanceof Integer) {
|
|
||||||
logText += getResources().getQuantityString(entryParcel.mType.getMsgId(),
|
|
||||||
(Integer) entryParcel.mParameters[0],
|
|
||||||
entryParcel.mParameters);
|
|
||||||
} else {
|
|
||||||
logText += getResources().getString(entryParcel.mType.getMsgId(),
|
|
||||||
entryParcel.mParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entryParcel instanceof SubLogEntryParcel) {
|
|
||||||
OperationResult subResult = ((SubLogEntryParcel) entryParcel).getSubResult();
|
|
||||||
LogEntryParcel subEntry = subResult.getLog().getLast();
|
|
||||||
if (subEntry != null) {
|
|
||||||
//the first line of log of subResult is same as entryParcel, so replace logText
|
|
||||||
logText = getPrintableOperationLog(subResult.getLog(), padding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return logText;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showExportLogDialog(final File exportFile) {
|
|
||||||
|
|
||||||
String title = this.getString(R.string.title_export_log);
|
|
||||||
|
|
||||||
String message = this.getString(R.string.specify_file_to_export_log_to);
|
|
||||||
|
|
||||||
FileHelper.saveFile(new FileHelper.FileDialogCallback() {
|
|
||||||
@Override
|
|
||||||
public void onFileSelected(File file, boolean checked) {
|
|
||||||
writeToLogFile(mResult.getLog(), file);
|
|
||||||
}
|
|
||||||
}, this.getActivity().getSupportFragmentManager(), title, message, exportFile, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LogExportResult extends OperationResult {
|
|
||||||
|
|
||||||
public static Creator<LogExportResult> CREATOR = new Creator<LogExportResult>() {
|
|
||||||
public LogExportResult createFromParcel(final Parcel source) {
|
|
||||||
return new LogExportResult(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogExportResult[] newArray(final int size) {
|
|
||||||
return new LogExportResult[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public LogExportResult(int result, OperationLog log) {
|
|
||||||
super(result, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* trivial but necessary to implement the Parcelable protocol.
|
|
||||||
*/
|
|
||||||
public LogExportResult(Parcel source) {
|
|
||||||
super(source);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -50,15 +49,11 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||||
* NFC devices.
|
* NFC devices.
|
||||||
* <p/>
|
|
||||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||||
* NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created
|
|
||||||
* internally and is NOT meant to be used by signing operations before adding signature time
|
|
||||||
*/
|
*/
|
||||||
public class NfcOperationActivity extends BaseNfcActivity {
|
public class NfcOperationActivity extends BaseNfcActivity {
|
||||||
|
|
||||||
@@ -84,8 +79,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void initTheme() {
|
protected void initTheme() {
|
||||||
mThemeChanger = new ThemeChanger(this);
|
mThemeChanger = new ThemeChanger(this);
|
||||||
mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog_SecurityToken,
|
mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog,
|
||||||
R.style.Theme_Keychain_Dark_Dialog_SecurityToken);
|
R.style.Theme_Keychain_Dark_Dialog);
|
||||||
mThemeChanger.changeTheme();
|
mThemeChanger.changeTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +96,6 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
|
|
||||||
mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
|
mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
|
||||||
|
|
||||||
if (mInputParcel == null) {
|
|
||||||
// for compatibility when used from OpenPgpService
|
|
||||||
// (or any place other than CryptoOperationHelper)
|
|
||||||
// NOTE: This CryptoInputParcel cannot be used for signing without adding signature time
|
|
||||||
mInputParcel = new CryptoInputParcel();
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitle(R.string.nfc_text);
|
setTitle(R.string.nfc_text);
|
||||||
|
|
||||||
vAnimator = (ViewAnimator) findViewById(R.id.view_animator);
|
vAnimator = (ViewAnimator) findViewById(R.id.view_animator);
|
||||||
@@ -163,9 +151,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NFC_SIGN: {
|
case NFC_SIGN: {
|
||||||
if (mInputParcel.getSignatureTime() == null) {
|
mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime);
|
||||||
mInputParcel.addSignatureTime(new Date());
|
|
||||||
}
|
|
||||||
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
||||||
byte[] hash = mRequiredInput.mInputData[i];
|
byte[] hash = mRequiredInput.mInputData[i];
|
||||||
int algo = mRequiredInput.mSignAlgos[i];
|
int algo = mRequiredInput.mSignAlgos[i];
|
||||||
@@ -240,7 +227,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
throw new IOException("Inappropriate key flags for smart card key.");
|
throw new IOException("Inappropriate key flags for smart card key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Is this really needed?
|
// TODO: Is this really used anywhere?
|
||||||
mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
|
mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,15 +248,13 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
protected void onNfcPostExecute() throws IOException {
|
protected void onNfcPostExecute() throws IOException {
|
||||||
if (mServiceIntent != null) {
|
if (mServiceIntent != null) {
|
||||||
// if we're triggered by OpenPgpService
|
// if we're triggered by OpenPgpService
|
||||||
|
// save updated cryptoInputParcel in cache
|
||||||
CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel);
|
CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel);
|
||||||
mServiceIntent.putExtra(EXTRA_CRYPTO_INPUT,
|
|
||||||
getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT));
|
|
||||||
setResult(RESULT_OK, mServiceIntent);
|
setResult(RESULT_OK, mServiceIntent);
|
||||||
} else {
|
} else {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
|
// send back the CryptoInputParcel we received
|
||||||
result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel);
|
result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel);
|
||||||
// send back the CryptoInputParcel we receive, to conform with the pattern in
|
|
||||||
// CryptoOperationHelper
|
|
||||||
setResult(RESULT_OK, result);
|
setResult(RESULT_OK, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,22 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||||
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||||
|
|
||||||
@@ -34,8 +44,14 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
|||||||
public class OrbotRequiredDialogActivity extends FragmentActivity
|
public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||||
implements OrbotHelper.DialogActions {
|
implements OrbotHelper.DialogActions {
|
||||||
|
|
||||||
|
public static final int MESSAGE_ORBOT_STARTED = 1;
|
||||||
|
public static final int MESSAGE_ORBOT_IGNORE = 2;
|
||||||
|
public static final int MESSAGE_DIALOG_CANCEL = 3;
|
||||||
|
|
||||||
// if suppplied and true will start Orbot directly without showing dialog
|
// if suppplied and true will start Orbot directly without showing dialog
|
||||||
public static final String EXTRA_START_ORBOT = "start_orbot";
|
public static final String EXTRA_START_ORBOT = "start_orbot";
|
||||||
|
// used for communicating results when triggered from a service
|
||||||
|
public static final String EXTRA_MESSENGER = "messenger";
|
||||||
|
|
||||||
// to provide any previous crypto input into which proxy preference is merged
|
// to provide any previous crypto input into which proxy preference is merged
|
||||||
public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input";
|
public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input";
|
||||||
@@ -43,6 +59,9 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
|||||||
public static final String RESULT_CRYPTO_INPUT = "result_crypto_input";
|
public static final String RESULT_CRYPTO_INPUT = "result_crypto_input";
|
||||||
|
|
||||||
private CryptoInputParcel mCryptoInputParcel;
|
private CryptoInputParcel mCryptoInputParcel;
|
||||||
|
private Messenger mMessenger;
|
||||||
|
|
||||||
|
private ProgressDialog mShowOrbotProgressDialog;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -54,9 +73,16 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
|||||||
mCryptoInputParcel = new CryptoInputParcel();
|
mCryptoInputParcel = new CryptoInputParcel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mMessenger = getIntent().getParcelableExtra(EXTRA_MESSENGER);
|
||||||
|
|
||||||
boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false);
|
boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false);
|
||||||
if (startOrbotDirect) {
|
if (startOrbotDirect) {
|
||||||
OrbotHelper.bestPossibleOrbotStart(this, this);
|
ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(this);
|
||||||
|
mShowOrbotProgressDialog = new ProgressDialog(theme);
|
||||||
|
mShowOrbotProgressDialog.setTitle(R.string.progress_starting_orbot);
|
||||||
|
mShowOrbotProgressDialog.setCancelable(false);
|
||||||
|
mShowOrbotProgressDialog.show();
|
||||||
|
OrbotHelper.bestPossibleOrbotStart(this, this, false);
|
||||||
} else {
|
} else {
|
||||||
showDialog();
|
showDialog();
|
||||||
}
|
}
|
||||||
@@ -84,13 +110,32 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
|||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case OrbotHelper.START_TOR_RESULT: {
|
case OrbotHelper.START_TOR_RESULT: {
|
||||||
onOrbotStarted(); // assumption that orbot was started, no way to tell for sure
|
dismissOrbotProgressDialog();
|
||||||
|
// unfortunately, this result is returned immediately and not when Orbot is started
|
||||||
|
// 10s is approximately the longest time Orbot has taken to start
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onOrbotStarted(); // assumption that orbot was started
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* for when Orbot is started without showing the dialog by the EXTRA_START_ORBOT intent extra
|
||||||
|
*/
|
||||||
|
private void dismissOrbotProgressDialog() {
|
||||||
|
if (mShowOrbotProgressDialog != null) {
|
||||||
|
mShowOrbotProgressDialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOrbotStarted() {
|
public void onOrbotStarted() {
|
||||||
|
dismissOrbotProgressDialog();
|
||||||
|
sendMessage(MESSAGE_ORBOT_STARTED);
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
// send back unmodified CryptoInputParcel for a retry
|
// send back unmodified CryptoInputParcel for a retry
|
||||||
intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
|
intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
|
||||||
@@ -100,6 +145,7 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNeutralButton() {
|
public void onNeutralButton() {
|
||||||
|
sendMessage(MESSAGE_ORBOT_IGNORE);
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
|
mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
|
||||||
intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
|
intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
|
||||||
@@ -109,6 +155,19 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
|
sendMessage(MESSAGE_DIALOG_CANCEL);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendMessage(int what) {
|
||||||
|
if (mMessenger != null) {
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
msg.what = what;
|
||||||
|
try {
|
||||||
|
mMessenger.send(msg);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(Constants.TAG, "Could not deliver message", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +71,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
* internally and is NOT meant to be used by signing operations before adding a signature time
|
* internally and is NOT meant to be used by signing operations before adding a signature time
|
||||||
*/
|
*/
|
||||||
public class PassphraseDialogActivity extends FragmentActivity {
|
public class PassphraseDialogActivity extends FragmentActivity {
|
||||||
|
|
||||||
public static final String RESULT_CRYPTO_INPUT = "result_data";
|
public static final String RESULT_CRYPTO_INPUT = "result_data";
|
||||||
|
|
||||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||||
@@ -261,6 +262,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
case DIVERT_TO_CARD:
|
case DIVERT_TO_CARD:
|
||||||
message = getString(R.string.yubikey_pin_for, userId);
|
message = getString(R.string.yubikey_pin_for, userId);
|
||||||
break;
|
break;
|
||||||
|
// special case: empty passphrase just returns the empty passphrase
|
||||||
|
case PASSPHRASE_EMPTY:
|
||||||
|
finishCaching(new Passphrase(""));
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
||||||
}
|
}
|
||||||
@@ -280,51 +284,42 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
mPassphraseText.setText(message);
|
mPassphraseText.setText(message);
|
||||||
|
|
||||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) {
|
// Hack to open keyboard.
|
||||||
// start pattern dialog and show progress circle here...
|
// This is the only method that I found to work across all Android versions
|
||||||
// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
|
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||||
// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
|
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||||
// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
|
// * opening in onActivityCreated does not work on Android 4.4
|
||||||
mInput.setVisibility(View.INVISIBLE);
|
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||||
mProgress.setVisibility(View.VISIBLE);
|
@Override
|
||||||
} else {
|
public void onFocusChange(View v, boolean hasFocus) {
|
||||||
// Hack to open keyboard.
|
mPassphraseEditText.post(new Runnable() {
|
||||||
// This is the only method that I found to work across all Android versions
|
@Override
|
||||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
public void run() {
|
||||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
if (getActivity() == null || mPassphraseEditText == null) {
|
||||||
// * opening in onActivityCreated does not work on Android 4.4
|
return;
|
||||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
|
||||||
mPassphraseEditText.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (getActivity() == null || mPassphraseEditText == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
|
||||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}
|
}
|
||||||
});
|
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||||
}
|
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
});
|
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||||
mPassphraseEditText.requestFocus();
|
}
|
||||||
|
});
|
||||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
|
||||||
mPassphraseEditText.setOnEditorActionListener(this);
|
|
||||||
|
|
||||||
if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
|
|
||||||
|| keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
|
|
||||||
mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
|
||||||
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
|
||||||
} else {
|
|
||||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
mPassphraseEditText.requestFocus();
|
||||||
|
|
||||||
|
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||||
|
mPassphraseEditText.setOnEditorActionListener(this);
|
||||||
|
|
||||||
|
if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
|
||||||
|
|| keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
|
||||||
|
mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||||
|
} else {
|
||||||
|
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||||
|
|
||||||
AlertDialog dialog = alert.create();
|
AlertDialog dialog = alert.create();
|
||||||
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||||
activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
|
activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
|
||||||
|
|||||||
@@ -1,577 +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.annotation.TargetApi;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.nfc.FormatException;
|
|
||||||
import android.nfc.NdefMessage;
|
|
||||||
import android.nfc.NdefRecord;
|
|
||||||
import android.nfc.NfcAdapter;
|
|
||||||
import android.nfc.Tag;
|
|
||||||
import android.nfc.tech.Ndef;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
|
||||||
import android.support.v4.app.FragmentTransaction;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
||||||
public class PassphraseWizardActivity extends FragmentActivity {
|
|
||||||
//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
|
|
||||||
//create or authenticate
|
|
||||||
public String selectedAction;
|
|
||||||
//for lockpattern
|
|
||||||
public static char[] pattern;
|
|
||||||
private static String passphrase = "";
|
|
||||||
//nfc string
|
|
||||||
private static byte[] output = new byte[8];
|
|
||||||
|
|
||||||
public static final String CREATE_METHOD = "create";
|
|
||||||
public static final String AUTHENTICATION = "authenticate";
|
|
||||||
|
|
||||||
NfcAdapter adapter;
|
|
||||||
PendingIntent pendingIntent;
|
|
||||||
IntentFilter writeTagFilters[];
|
|
||||||
boolean writeMode;
|
|
||||||
Tag myTag;
|
|
||||||
boolean writeNFC = false;
|
|
||||||
boolean readNFC = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
if (getActionBar() != null) {
|
|
||||||
getActionBar().setTitle(R.string.unlock_method);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedAction = getIntent().getAction();
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
SelectMethods selectMethods = new SelectMethods();
|
|
||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
transaction.add(R.id.fragmentContainer, selectMethods).commit();
|
|
||||||
}
|
|
||||||
setContentView(R.layout.passphrase_wizard);
|
|
||||||
|
|
||||||
adapter = NfcAdapter.getDefaultAdapter(this);
|
|
||||||
if (adapter != null) {
|
|
||||||
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, PassphraseWizardActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
|
|
||||||
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
|
|
||||||
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
|
|
||||||
writeTagFilters = new IntentFilter[]{tagDetected};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void noPassphrase(View view) {
|
|
||||||
passphrase = "";
|
|
||||||
Toast.makeText(this, R.string.no_passphrase_set, Toast.LENGTH_SHORT).show();
|
|
||||||
this.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void passphrase(View view) {
|
|
||||||
Passphrase passphrase = new Passphrase();
|
|
||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
transaction.replace(R.id.fragmentContainer, passphrase).addToBackStack(null).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startLockpattern(View view) {
|
|
||||||
if (getActionBar() != null) {
|
|
||||||
getActionBar().setTitle(R.string.draw_lockpattern);
|
|
||||||
}
|
|
||||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
|
||||||
// LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
|
|
||||||
|
|
||||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel(View view) {
|
|
||||||
this.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void savePassphrase(View view) {
|
|
||||||
EditText passphrase = (EditText) findViewById(R.id.passphrase);
|
|
||||||
passphrase.setError(null);
|
|
||||||
String pw = passphrase.getText().toString();
|
|
||||||
//check and save passphrase
|
|
||||||
if (selectedAction.equals(CREATE_METHOD)) {
|
|
||||||
EditText passphraseAgain = (EditText) findViewById(R.id.passphraseAgain);
|
|
||||||
passphraseAgain.setError(null);
|
|
||||||
String pwAgain = passphraseAgain.getText().toString();
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(pw)) {
|
|
||||||
if (!TextUtils.isEmpty(pwAgain)) {
|
|
||||||
if (pw.equals(pwAgain)) {
|
|
||||||
PassphraseWizardActivity.passphrase = pw;
|
|
||||||
Toast.makeText(this, getString(R.string.passphrase_saved), Toast.LENGTH_SHORT).show();
|
|
||||||
this.finish();
|
|
||||||
} else {
|
|
||||||
passphrase.setError(getString(R.string.passphrase_invalid));
|
|
||||||
passphrase.requestFocus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
passphraseAgain.setError(getString(R.string.missing_passphrase));
|
|
||||||
passphraseAgain.requestFocus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
passphrase.setError(getString(R.string.missing_passphrase));
|
|
||||||
passphrase.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//check for right passphrase
|
|
||||||
if (selectedAction.equals(AUTHENTICATION)) {
|
|
||||||
if (pw.equals(PassphraseWizardActivity.passphrase)) {
|
|
||||||
Toast.makeText(this, getString(R.string.unlocked), Toast.LENGTH_SHORT).show();
|
|
||||||
this.finish();
|
|
||||||
} else {
|
|
||||||
passphrase.setError(getString(R.string.passphrase_invalid));
|
|
||||||
passphrase.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NFC(View view) {
|
|
||||||
if (adapter != null) {
|
|
||||||
if (getActionBar() != null) {
|
|
||||||
getActionBar().setTitle(R.string.nfc_title);
|
|
||||||
}
|
|
||||||
NFCFragment nfc = new NFCFragment();
|
|
||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
transaction.replace(R.id.fragmentContainer, nfc).addToBackStack(null).commit();
|
|
||||||
|
|
||||||
//if you want to create a new method or just authenticate
|
|
||||||
if (CREATE_METHOD.equals(selectedAction)) {
|
|
||||||
writeNFC = true;
|
|
||||||
} else if (AUTHENTICATION.equals(selectedAction)) {
|
|
||||||
readNFC = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!adapter.isEnabled()) {
|
|
||||||
showAlertDialog(getString(R.string.enable_nfc), true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showAlertDialog(getString(R.string.no_nfc_support), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNewIntent(Intent intent) {
|
|
||||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
|
||||||
myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
|
||||||
|
|
||||||
if (writeNFC && CREATE_METHOD.equals(selectedAction)) {
|
|
||||||
//write new password on NFC tag
|
|
||||||
try {
|
|
||||||
if (myTag != null) {
|
|
||||||
write(myTag);
|
|
||||||
writeNFC = false; //just write once
|
|
||||||
Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show();
|
|
||||||
//advance to lockpattern
|
|
||||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
|
||||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
|
||||||
}
|
|
||||||
} catch (IOException | FormatException e) {
|
|
||||||
Log.e(Constants.TAG, "Failed to write on NFC tag", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (readNFC && AUTHENTICATION.equals(selectedAction)) {
|
|
||||||
//read pw from NFC tag
|
|
||||||
try {
|
|
||||||
if (myTag != null) {
|
|
||||||
//if tag detected, read tag
|
|
||||||
String pwtag = read(myTag);
|
|
||||||
if (output != null && pwtag.equals(output.toString())) {
|
|
||||||
|
|
||||||
//passwort matches, go to next view
|
|
||||||
Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show();
|
|
||||||
|
|
||||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
|
||||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
|
||||||
readNFC = false; //just once
|
|
||||||
} else {
|
|
||||||
//passwort doesnt match
|
|
||||||
TextView nfc = (TextView) findViewById(R.id.nfcText);
|
|
||||||
nfc.setText(R.string.nfc_wrong_tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException | FormatException e) {
|
|
||||||
Log.e(Constants.TAG, "Failed to read NFC tag", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void write(Tag tag) throws IOException, FormatException {
|
|
||||||
//generate new random key and write them on the tag
|
|
||||||
SecureRandom sr = new SecureRandom();
|
|
||||||
sr.nextBytes(output);
|
|
||||||
NdefRecord[] records = {createRecord(output.toString())};
|
|
||||||
NdefMessage message = new NdefMessage(records);
|
|
||||||
Ndef ndef = Ndef.get(tag);
|
|
||||||
ndef.connect();
|
|
||||||
ndef.writeNdefMessage(message);
|
|
||||||
ndef.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String read(Tag tag) throws IOException, FormatException {
|
|
||||||
//read string from tag
|
|
||||||
String password = null;
|
|
||||||
Ndef ndef = Ndef.get(tag);
|
|
||||||
ndef.connect();
|
|
||||||
NdefMessage ndefMessage = ndef.getCachedNdefMessage();
|
|
||||||
|
|
||||||
NdefRecord[] records = ndefMessage.getRecords();
|
|
||||||
for (NdefRecord ndefRecord : records) {
|
|
||||||
if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
|
|
||||||
try {
|
|
||||||
password = readText(ndefRecord);
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
Log.e(Constants.TAG, "Failed to read password from tag", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ndef.close();
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readText(NdefRecord record) throws UnsupportedEncodingException {
|
|
||||||
//low-level method for reading nfc
|
|
||||||
byte[] payload = record.getPayload();
|
|
||||||
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
|
|
||||||
int languageCodeLength = payload[0] & 0063;
|
|
||||||
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
private NdefRecord createRecord(String text) throws UnsupportedEncodingException {
|
|
||||||
//low-level method for writing nfc
|
|
||||||
String lang = "en";
|
|
||||||
byte[] textBytes = text.getBytes();
|
|
||||||
byte[] langBytes = lang.getBytes("US-ASCII");
|
|
||||||
int langLength = langBytes.length;
|
|
||||||
int textLength = textBytes.length;
|
|
||||||
byte[] payload = new byte[1 + langLength + textLength];
|
|
||||||
|
|
||||||
// set status byte (see NDEF spec for actual bits)
|
|
||||||
payload[0] = (byte) langLength;
|
|
||||||
// copy langbytes and textbytes into payload
|
|
||||||
System.arraycopy(langBytes, 0, payload, 1, langLength);
|
|
||||||
System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);
|
|
||||||
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showAlertDialog(String message, boolean nfc) {
|
|
||||||
//This method shows an AlertDialog
|
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
|
||||||
alert.setTitle("Information").setMessage(message).setPositiveButton("Ok",
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (nfc) {
|
|
||||||
|
|
||||||
alert.setNeutralButton(R.string.nfc_settings,
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
//pause this app and free nfc intent
|
|
||||||
super.onPause();
|
|
||||||
if (adapter != null) {
|
|
||||||
WriteModeOff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
//resume this app and get nfc intent
|
|
||||||
super.onResume();
|
|
||||||
if (adapter != null) {
|
|
||||||
WriteModeOn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteModeOn() {
|
|
||||||
//enable nfc for this view
|
|
||||||
writeMode = true;
|
|
||||||
adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteModeOff() {
|
|
||||||
//disable nfc for this view
|
|
||||||
writeMode = false;
|
|
||||||
adapter.disableForegroundDispatch(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SelectMethods extends Fragment {
|
|
||||||
// private OnFragmentInteractionListener mListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this factory method to create a new instance of
|
|
||||||
* this fragment using the provided parameters.
|
|
||||||
*/
|
|
||||||
public SelectMethods() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (getActivity().getActionBar() != null) {
|
|
||||||
getActivity().getActionBar().setTitle(R.string.unlock_method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
return inflater.inflate(R.layout.passphrase_wizard_fragment_select_methods, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onAttach(Activity activity) {
|
|
||||||
// super.onAttach(activity);
|
|
||||||
// try {
|
|
||||||
// mListener = (OnFragmentInteractionListener) activity;
|
|
||||||
// } catch (ClassCastException e) {
|
|
||||||
// throw new ClassCastException(activity.toString()
|
|
||||||
// + " must implement OnFragmentInteractionListener");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onDetach() {
|
|
||||||
// super.onDetach();
|
|
||||||
// mListener = null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface must be implemented by activities that contain this
|
|
||||||
* fragment to allow an interaction in this fragment to be communicated
|
|
||||||
* to the activity and potentially other fragments contained in that
|
|
||||||
* activity.
|
|
||||||
* <p/>
|
|
||||||
* See the Android Training lesson <a href=
|
|
||||||
* "http://developer.android.com/training/basics/fragments/communicating.html"
|
|
||||||
* >Communicating with Other Fragments</a> for more information.
|
|
||||||
*/
|
|
||||||
// public static interface OnFragmentInteractionListener {
|
|
||||||
// public void onFragmentInteraction(Uri uri);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * A simple {@link android.support.v4.app.Fragment} subclass.
|
|
||||||
// * Activities that contain this fragment must implement the
|
|
||||||
// * {@link com.haibison.android.lockpattern.Passphrase.OnFragmentInteractionListener} interface
|
|
||||||
// * to handle interaction events.
|
|
||||||
// */
|
|
||||||
public static class Passphrase extends Fragment {
|
|
||||||
|
|
||||||
// private OnFragmentInteractionListener mListener;
|
|
||||||
|
|
||||||
public Passphrase() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
View view = inflater.inflate(R.layout.passphrase_wizard_fragment_passphrase, container, false);
|
|
||||||
EditText passphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
|
|
||||||
TextView passphraseText = (TextView) view.findViewById(R.id.passphraseText);
|
|
||||||
TextView passphraseTextAgain = (TextView) view.findViewById(R.id.passphraseTextAgain);
|
|
||||||
String selectedAction = getActivity().getIntent().getAction();
|
|
||||||
if (selectedAction.equals(AUTHENTICATION)) {
|
|
||||||
passphraseAgain.setVisibility(View.GONE);
|
|
||||||
passphraseTextAgain.setVisibility(View.GONE);
|
|
||||||
passphraseText.setText(R.string.enter_passphrase);
|
|
||||||
// getActivity().getActionBar().setTitle(R.string.enter_passphrase);
|
|
||||||
} else if (selectedAction.equals(CREATE_METHOD)) {
|
|
||||||
passphraseAgain.setVisibility(View.VISIBLE);
|
|
||||||
passphraseTextAgain.setVisibility(View.VISIBLE);
|
|
||||||
passphraseText.setText(R.string.passphrase);
|
|
||||||
// getActivity().getActionBar().setTitle(R.string.set_passphrase);
|
|
||||||
}
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onAttach(Activity activity) {
|
|
||||||
// super.onAttach(activity);
|
|
||||||
// try {
|
|
||||||
// mListener = (OnFragmentInteractionListener) activity;
|
|
||||||
// } catch (ClassCastException e) {
|
|
||||||
// throw new ClassCastException(activity.toString()
|
|
||||||
// + " must implement OnFragmentInteractionListener");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onDetach() {
|
|
||||||
// super.onDetach();
|
|
||||||
// mListener = null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * This interface must be implemented by activities that contain this
|
|
||||||
// * fragment to allow an interaction in this fragment to be communicated
|
|
||||||
// * to the activity and potentially other fragments contained in that
|
|
||||||
// * activity.
|
|
||||||
// * <p/>
|
|
||||||
// * See the Android Training lesson <a href=
|
|
||||||
// * "http://developer.android.com/training/basics/fragments/communicating.html"
|
|
||||||
// * >Communicating with Other Fragments</a> for more information.
|
|
||||||
// */
|
|
||||||
// public interface OnFragmentInteractionListener {
|
|
||||||
// public void onFragmentInteraction(Uri uri);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple {@link android.support.v4.app.Fragment} subclass.
|
|
||||||
* Activities that contain this fragment must implement the
|
|
||||||
* interface
|
|
||||||
* to handle interaction events.
|
|
||||||
* Use the method to
|
|
||||||
* create an instance of this fragment.
|
|
||||||
*/
|
|
||||||
public static class NFCFragment extends Fragment {
|
|
||||||
// TODO: Rename parameter arguments, choose names that match
|
|
||||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
|
||||||
private static final String ARG_PARAM1 = "param1";
|
|
||||||
private static final String ARG_PARAM2 = "param2";
|
|
||||||
|
|
||||||
// TODO: Rename and change types of parameters
|
|
||||||
private String mParam1;
|
|
||||||
private String mParam2;
|
|
||||||
|
|
||||||
// private OnFragmentInteractionListener mListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this factory method to create a new instance of
|
|
||||||
* this fragment using the provided parameters.
|
|
||||||
*
|
|
||||||
* @param param1 Parameter 1.
|
|
||||||
* @param param2 Parameter 2.
|
|
||||||
* @return A new instance of fragment SelectMethods.
|
|
||||||
*/
|
|
||||||
// TODO: Rename and change types and number of parameters
|
|
||||||
public static NFCFragment newInstance(String param1, String param2) {
|
|
||||||
NFCFragment fragment = new NFCFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString(ARG_PARAM1, param1);
|
|
||||||
args.putString(ARG_PARAM2, param2);
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NFCFragment() {
|
|
||||||
// Required empty public constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
if (getArguments() != null) {
|
|
||||||
mParam1 = getArguments().getString(ARG_PARAM1);
|
|
||||||
mParam2 = getArguments().getString(ARG_PARAM2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
return inflater.inflate(R.layout.passphrase_wizard_fragment_nfc, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: Rename method, update argument and hook method into UI event
|
|
||||||
// public void onButtonPressed(Uri uri) {
|
|
||||||
// if (mListener != null) {
|
|
||||||
// mListener.onFragmentInteraction(uri);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onAttach(Activity activity) {
|
|
||||||
// super.onAttach(activity);
|
|
||||||
// try {
|
|
||||||
// mListener = (OnFragmentInteractionListener) activity;
|
|
||||||
// } catch (ClassCastException e) {
|
|
||||||
// throw new ClassCastException(activity.toString()
|
|
||||||
// + " must implement OnFragmentInteractionListener");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onDetach() {
|
|
||||||
// super.onDetach();
|
|
||||||
// mListener = null;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -18,11 +18,12 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
@@ -31,6 +32,8 @@ import android.preference.Preference;
|
|||||||
import android.preference.PreferenceActivity;
|
import android.preference.PreferenceActivity;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -74,7 +77,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
|
|
||||||
String action = getIntent().getAction();
|
String action = getIntent().getAction();
|
||||||
|
|
||||||
if (action != null && action.equals(ACTION_PREFS_CLOUD)) {
|
if (ACTION_PREFS_CLOUD.equals(action)) {
|
||||||
addPreferencesFromResource(R.xml.cloud_search_prefs);
|
addPreferencesFromResource(R.xml.cloud_search_prefs);
|
||||||
|
|
||||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
||||||
@@ -91,14 +94,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
initializeSearchKeyserver(
|
initializeSearchKeyserver(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
||||||
);
|
);
|
||||||
initializeSearchKeybase(
|
initializeSearchKeybase(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
||||||
);
|
);
|
||||||
|
|
||||||
} else if (action != null && action.equals(ACTION_PREFS_ADV)) {
|
} else if (ACTION_PREFS_ADV.equals(action)) {
|
||||||
addPreferencesFromResource(R.xml.adv_preferences);
|
addPreferencesFromResource(R.xml.passphrase_preferences);
|
||||||
|
|
||||||
initializePassphraseCacheSubs(
|
initializePassphraseCacheSubs(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
|
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
|
||||||
@@ -112,7 +115,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
initializeUseNumKeypadForYubiKeyPin(
|
initializeUseNumKeypadForYubiKeyPin(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
||||||
|
|
||||||
} else if (action != null && action.equals(ACTION_PREFS_GUI)) {
|
} else if (ACTION_PREFS_GUI.equals(action)) {
|
||||||
addPreferencesFromResource(R.xml.gui_preferences);
|
addPreferencesFromResource(R.xml.gui_preferences);
|
||||||
|
|
||||||
initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
|
initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
|
||||||
@@ -192,10 +195,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
initializeSearchKeyserver(
|
initializeSearchKeyserver(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
||||||
);
|
);
|
||||||
initializeSearchKeybase(
|
initializeSearchKeybase(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,14 +222,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
/**
|
/**
|
||||||
* This fragment shows the PIN/password preferences
|
* This fragment shows the PIN/password preferences
|
||||||
*/
|
*/
|
||||||
public static class AdvancedPrefsFragment extends PreferenceFragment {
|
public static class PassphrasePrefsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
addPreferencesFromResource(R.xml.adv_preferences);
|
addPreferencesFromResource(R.xml.passphrase_preferences);
|
||||||
|
|
||||||
initializePassphraseCacheSubs(
|
initializePassphraseCacheSubs(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
|
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
|
||||||
@@ -253,8 +256,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Initializer {
|
public static class Initializer {
|
||||||
private CheckBoxPreference mUseTor;
|
private SwitchPreference mUseTor;
|
||||||
private CheckBoxPreference mUseNormalProxy;
|
private SwitchPreference mUseNormalProxy;
|
||||||
private EditTextPreference mProxyHost;
|
private EditTextPreference mProxyHost;
|
||||||
private EditTextPreference mProxyPort;
|
private EditTextPreference mProxyPort;
|
||||||
private ListPreference mProxyType;
|
private ListPreference mProxyType;
|
||||||
@@ -290,8 +293,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
mActivity.addPreferencesFromResource(R.xml.proxy_prefs);
|
mActivity.addPreferencesFromResource(R.xml.proxy_prefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY);
|
mUseTor = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY);
|
||||||
mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY);
|
mUseNormalProxy = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY);
|
||||||
mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST);
|
mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST);
|
||||||
mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT);
|
mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT);
|
||||||
mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE);
|
mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE);
|
||||||
@@ -467,11 +470,121 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This fragment shows the keyserver/contacts sync preferences
|
||||||
|
*/
|
||||||
|
public static class SyncPrefsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Load the preferences from an XML resource
|
||||||
|
addPreferencesFromResource(R.xml.sync_preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
// this needs to be done in onResume since the user can change sync values from Android
|
||||||
|
// settings and we need to reflect that change when the user navigates back
|
||||||
|
AccountManager manager = AccountManager.get(getActivity());
|
||||||
|
final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
|
||||||
|
// for keyserver sync
|
||||||
|
initializeSyncCheckBox(
|
||||||
|
(SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
|
||||||
|
account,
|
||||||
|
Constants.PROVIDER_AUTHORITY
|
||||||
|
);
|
||||||
|
// for contacts sync
|
||||||
|
initializeSyncCheckBox(
|
||||||
|
(SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS),
|
||||||
|
account,
|
||||||
|
ContactsContract.AUTHORITY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
|
||||||
|
final Account account,
|
||||||
|
final String authority) {
|
||||||
|
boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
|
||||||
|
syncCheckBox.setChecked(syncEnabled);
|
||||||
|
setSummary(syncCheckBox, authority, syncEnabled);
|
||||||
|
|
||||||
|
syncCheckBox.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
boolean syncEnabled = (Boolean) newValue;
|
||||||
|
if (syncEnabled) {
|
||||||
|
ContentResolver.setSyncAutomatically(account, authority, true);
|
||||||
|
} else {
|
||||||
|
// disable syncs
|
||||||
|
ContentResolver.setSyncAutomatically(account, authority, false);
|
||||||
|
// cancel any ongoing/pending syncs
|
||||||
|
ContentResolver.cancelSync(account, authority);
|
||||||
|
}
|
||||||
|
setSummary(syncCheckBox, authority, syncEnabled);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSummary(SwitchPreference syncCheckBox, String authority,
|
||||||
|
boolean checked) {
|
||||||
|
switch (authority) {
|
||||||
|
case Constants.PROVIDER_AUTHORITY: {
|
||||||
|
if (checked) {
|
||||||
|
syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_on);
|
||||||
|
} else {
|
||||||
|
syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_off);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ContactsContract.AUTHORITY: {
|
||||||
|
if (checked) {
|
||||||
|
syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_on);
|
||||||
|
} else {
|
||||||
|
syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_off);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This fragment shows experimental features
|
||||||
|
*/
|
||||||
|
public static class ExperimentalPrefsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Load the preferences from an XML resource
|
||||||
|
addPreferencesFromResource(R.xml.experimental_preferences);
|
||||||
|
|
||||||
|
initializeExperimentalEnableWordConfirm(
|
||||||
|
(SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM));
|
||||||
|
|
||||||
|
initializeExperimentalEnableLinkedIdentities(
|
||||||
|
(SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES));
|
||||||
|
|
||||||
|
initializeExperimentalEnableKeybase(
|
||||||
|
(SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_KEYBASE));
|
||||||
|
|
||||||
|
initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean isValidFragment(String fragmentName) {
|
protected boolean isValidFragment(String fragmentName) {
|
||||||
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
|
return PassphrasePrefsFragment.class.getName().equals(fragmentName)
|
||||||
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|
||||||
|| ProxyPrefsFragment.class.getName().equals(fragmentName)
|
|| ProxyPrefsFragment.class.getName().equals(fragmentName)
|
||||||
|| GuiPrefsFragment.class.getName().equals(fragmentName)
|
|| GuiPrefsFragment.class.getName().equals(fragmentName)
|
||||||
|
|| SyncPrefsFragment.class.getName().equals(fragmentName)
|
||||||
|
|| ExperimentalPrefsFragment.class.getName().equals(fragmentName)
|
||||||
|| super.isValidFragment(fragmentName);
|
|| super.isValidFragment(fragmentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,11 +615,13 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
|
|
||||||
private static void initializeTheme(final ListPreference mTheme) {
|
private static void initializeTheme(final ListPreference mTheme) {
|
||||||
mTheme.setValue(sPreferences.getTheme());
|
mTheme.setValue(sPreferences.getTheme());
|
||||||
mTheme.setSummary(mTheme.getEntry());
|
mTheme.setSummary(mTheme.getEntry() + "\n"
|
||||||
|
+ mTheme.getContext().getString(R.string.label_experimental_settings_theme_summary));
|
||||||
mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mTheme.setValue((String) newValue);
|
mTheme.setValue((String) newValue);
|
||||||
mTheme.setSummary(mTheme.getEntry());
|
mTheme.setSummary(mTheme.getEntry() + "\n"
|
||||||
|
+ mTheme.getContext().getString(R.string.label_experimental_settings_theme_summary));
|
||||||
sPreferences.setTheme((String) newValue);
|
sPreferences.setTheme((String) newValue);
|
||||||
|
|
||||||
((SettingsActivity) mTheme.getContext()).recreate();
|
((SettingsActivity) mTheme.getContext()).recreate();
|
||||||
@@ -516,7 +631,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) {
|
private static void initializeSearchKeyserver(final SwitchPreference mSearchKeyserver) {
|
||||||
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
|
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
|
||||||
mSearchKeyserver.setChecked(prefs.searchKeyserver);
|
mSearchKeyserver.setChecked(prefs.searchKeyserver);
|
||||||
mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@@ -529,7 +644,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializeSearchKeybase(final CheckBoxPreference mSearchKeybase) {
|
private static void initializeSearchKeybase(final SwitchPreference mSearchKeybase) {
|
||||||
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
|
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
|
||||||
mSearchKeybase.setChecked(prefs.searchKeybase);
|
mSearchKeybase.setChecked(prefs.searchKeybase);
|
||||||
mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@@ -571,4 +686,37 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void initializeExperimentalEnableWordConfirm(final SwitchPreference mExperimentalEnableWordConfirm) {
|
||||||
|
mExperimentalEnableWordConfirm.setChecked(sPreferences.getExperimentalEnableWordConfirm());
|
||||||
|
mExperimentalEnableWordConfirm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
mExperimentalEnableWordConfirm.setChecked((Boolean) newValue);
|
||||||
|
sPreferences.setExperimentalEnableWordConfirm((Boolean) newValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initializeExperimentalEnableLinkedIdentities(final SwitchPreference mExperimentalEnableLinkedIdentities) {
|
||||||
|
mExperimentalEnableLinkedIdentities.setChecked(sPreferences.getExperimentalEnableLinkedIdentities());
|
||||||
|
mExperimentalEnableLinkedIdentities.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
mExperimentalEnableLinkedIdentities.setChecked((Boolean) newValue);
|
||||||
|
sPreferences.setExperimentalEnableLinkedIdentities((Boolean) newValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initializeExperimentalEnableKeybase(final SwitchPreference mExperimentalKeybase) {
|
||||||
|
mExperimentalKeybase.setChecked(sPreferences.getExperimentalEnableKeybase());
|
||||||
|
mExperimentalKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
mExperimentalKeybase.setChecked((Boolean) newValue);
|
||||||
|
sPreferences.setExperimentalEnableKeybase((Boolean) newValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
@@ -34,6 +35,19 @@ public class SettingsKeyServerActivity extends BaseActivity {
|
|||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
|
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
|
||||||
loadFragment(savedInstanceState, servers);
|
loadFragment(savedInstanceState, servers);
|
||||||
|
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import android.animation.ArgbEvaluator;
|
import android.animation.ArgbEvaluator;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -32,6 +37,9 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
|
import android.support.design.widget.AppBarLayout;
|
||||||
|
import android.support.design.widget.CollapsingToolbarLayout;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
@@ -45,14 +53,13 @@ import android.view.animation.AlphaAnimation;
|
|||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.Animation.AnimationListener;
|
import android.view.animation.Animation.AnimationListener;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.getbase.floatingactionbutton.FloatingActionButton;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
@@ -65,8 +72,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
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.ImportKeyringParcel;
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||||
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;
|
||||||
@@ -80,8 +89,6 @@ 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;
|
|
||||||
|
|
||||||
public class ViewKeyActivity extends BaseNfcActivity implements
|
public class ViewKeyActivity extends BaseNfcActivity implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor>,
|
LoaderManager.LoaderCallbacks<Cursor>,
|
||||||
@@ -97,6 +104,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
static final int REQUEST_DELETE = 4;
|
static final int REQUEST_DELETE = 4;
|
||||||
|
|
||||||
public static final String EXTRA_DISPLAY_RESULT = "display_result";
|
public static final String EXTRA_DISPLAY_RESULT = "display_result";
|
||||||
|
public static final String EXTRA_LINKED_TRANSITION = "linked_transition";
|
||||||
|
|
||||||
ProviderHelper mProviderHelper;
|
ProviderHelper mProviderHelper;
|
||||||
|
|
||||||
@@ -107,16 +115,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
private ArrayList<ParcelableKeyRing> mKeyList;
|
private ArrayList<ParcelableKeyRing> mKeyList;
|
||||||
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
|
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
|
||||||
|
|
||||||
private TextView mName;
|
|
||||||
private TextView mStatusText;
|
private TextView mStatusText;
|
||||||
private ImageView mStatusImage;
|
private ImageView mStatusImage;
|
||||||
private RelativeLayout mBigToolbar;
|
private AppBarLayout mAppBarLayout;
|
||||||
|
private CollapsingToolbarLayout mCollapsingToolbarLayout;
|
||||||
|
|
||||||
private ImageButton mActionEncryptFile;
|
private ImageButton mActionEncryptFile;
|
||||||
private ImageButton mActionEncryptText;
|
private ImageButton mActionEncryptText;
|
||||||
private ImageButton mActionNfc;
|
private ImageButton mActionNfc;
|
||||||
private FloatingActionButton mFab;
|
private FloatingActionButton mFab;
|
||||||
private ImageView mPhoto;
|
private ImageView mPhoto;
|
||||||
|
private FrameLayout mPhotoLayout;
|
||||||
private ImageView mQrCode;
|
private ImageView mQrCode;
|
||||||
private CardView mQrCodeLayout;
|
private CardView mQrCodeLayout;
|
||||||
|
|
||||||
@@ -156,16 +165,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
|
|
||||||
setTitle(null);
|
setTitle(null);
|
||||||
|
|
||||||
mName = (TextView) findViewById(R.id.view_key_name);
|
|
||||||
mStatusText = (TextView) findViewById(R.id.view_key_status);
|
mStatusText = (TextView) findViewById(R.id.view_key_status);
|
||||||
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
|
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
|
||||||
mBigToolbar = (RelativeLayout) findViewById(R.id.toolbar_big);
|
mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout);
|
||||||
|
mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
|
||||||
|
|
||||||
mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files);
|
mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files);
|
||||||
mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text);
|
mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text);
|
||||||
mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc);
|
mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc);
|
||||||
mFab = (FloatingActionButton) findViewById(R.id.fab);
|
mFab = (FloatingActionButton) findViewById(R.id.fab);
|
||||||
mPhoto = (ImageView) findViewById(R.id.view_key_photo);
|
mPhoto = (ImageView) findViewById(R.id.view_key_photo);
|
||||||
|
mPhotoLayout = (FrameLayout) findViewById(R.id.view_key_photo_layout);
|
||||||
mQrCode = (ImageView) findViewById(R.id.view_key_qr_code);
|
mQrCode = (ImageView) findViewById(R.id.view_key_qr_code);
|
||||||
mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout);
|
mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout);
|
||||||
|
|
||||||
@@ -287,13 +297,26 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean linkedTransition = getIntent().getBooleanExtra(EXTRA_LINKED_TRANSITION, false);
|
||||||
|
if (linkedTransition && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
postponeEnterTransition();
|
||||||
|
}
|
||||||
|
|
||||||
FragmentManager manager = getSupportFragmentManager();
|
FragmentManager manager = getSupportFragmentManager();
|
||||||
// Create an instance of the fragment
|
// Create an instance of the fragment
|
||||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri);
|
final ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri,
|
||||||
|
linkedTransition ? PostponeType.LINKED : PostponeType.NONE);
|
||||||
manager.beginTransaction()
|
manager.beginTransaction()
|
||||||
.replace(R.id.view_key_fragment, frag)
|
.replace(R.id.view_key_fragment, frag)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) {
|
||||||
|
final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri);
|
||||||
|
manager.beginTransaction()
|
||||||
|
.replace(R.id.view_key_keybase_fragment, keybaseFrag)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
// need to postpone loading of the yubikey fragment until after mMasterKeyId
|
// need to postpone loading of the yubikey fragment until after mMasterKeyId
|
||||||
// is available, but we mark here that this should be done
|
// is available, but we mark here that this should be done
|
||||||
mShowYubikeyAfterCreation = true;
|
mShowYubikeyAfterCreation = true;
|
||||||
@@ -344,12 +367,23 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case R.id.menu_key_view_add_linked_identity: {
|
||||||
|
Intent intent = new Intent(this, LinkedIdWizard.class);
|
||||||
|
intent.setData(mDataUri);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case R.id.menu_key_view_edit: {
|
case R.id.menu_key_view_edit: {
|
||||||
editKey(mDataUri);
|
editKey(mDataUri);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.menu_key_view_certify_fingerprint: {
|
case R.id.menu_key_view_certify_fingerprint: {
|
||||||
certifyFingeprint(mDataUri);
|
certifyFingeprint(mDataUri, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.menu_key_view_certify_fingerprint_word: {
|
||||||
|
certifyFingeprint(mDataUri, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,10 +394,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
|
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
|
||||||
editKey.setVisible(mIsSecret);
|
editKey.setVisible(mIsSecret);
|
||||||
|
|
||||||
MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
|
MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
|
||||||
exportKey.setVisible(mIsSecret);
|
exportKey.setVisible(mIsSecret);
|
||||||
|
|
||||||
|
MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity);
|
||||||
|
addLinked.setVisible(mIsSecret
|
||||||
|
&& Preferences.getPreferences(this).getExperimentalEnableLinkedIdentities());
|
||||||
|
|
||||||
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
|
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
|
||||||
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);
|
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);
|
||||||
|
MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word);
|
||||||
|
certifyFingerprintWord.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked
|
||||||
|
&& Preferences.getPreferences(this).getExperimentalEnableWordConfirm());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -375,16 +418,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT);
|
startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void certifyFingeprint(Uri dataUri) {
|
private void certifyFingeprint(Uri dataUri, boolean enableWordConfirm) {
|
||||||
Intent intent = new Intent(this, CertifyFingerprintActivity.class);
|
Intent intent = new Intent(this, CertifyFingerprintActivity.class);
|
||||||
intent.setData(dataUri);
|
intent.setData(dataUri);
|
||||||
|
intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm);
|
||||||
|
|
||||||
startActivityForResult(intent, REQUEST_CERTIFY);
|
startActivityForResult(intent, REQUEST_CERTIFY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void certifyImmediate() {
|
private void certifyImmediate() {
|
||||||
Intent intent = new Intent(this, CertifyKeyActivity.class);
|
Intent intent = new Intent(this, CertifyKeyActivity.class);
|
||||||
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId});
|
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { mMasterKeyId });
|
||||||
|
|
||||||
startActivityForResult(intent, REQUEST_CERTIFY);
|
startActivityForResult(intent, REQUEST_CERTIFY);
|
||||||
}
|
}
|
||||||
@@ -413,7 +457,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
|
|
||||||
private void backupToFile() {
|
private void backupToFile() {
|
||||||
new ExportHelper(this).showExportKeysDialog(
|
new ExportHelper(this).showExportKeysDialog(
|
||||||
mMasterKeyId, Constants.Path.APP_DIR_FILE, true);
|
mMasterKeyId, new File(Constants.Path.APP_DIR,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteKey() {
|
private void deleteKey() {
|
||||||
@@ -437,13 +482,13 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_QR_FINGERPRINT: {
|
case REQUEST_QR_FINGERPRINT: {
|
||||||
|
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If there is an EXTRA_RESULT, that's an error. Just show it.
|
// If there is an EXTRA_RESULT, that's an error. Just show it.
|
||||||
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||||
@@ -465,11 +510,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
case REQUEST_BACKUP: {
|
case REQUEST_BACKUP: {
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
backupToFile();
|
backupToFile();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case REQUEST_CERTIFY: {
|
case REQUEST_CERTIFY: {
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||||
result.createNotify(this).show();
|
result.createNotify(this).show();
|
||||||
@@ -478,6 +531,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
case REQUEST_DELETE: {
|
case REQUEST_DELETE: {
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setResult(RESULT_OK, data);
|
setResult(RESULT_OK, data);
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
@@ -530,7 +587,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}, R.string.snack_yubikey_view).show();
|
}, R.string.snack_yubikey_view).show();
|
||||||
|
|
||||||
// and if it's not found, offer import
|
// and if it's not found, offer import
|
||||||
} catch (PgpKeyNotFoundException e) {
|
} catch (PgpKeyNotFoundException e) {
|
||||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||||
@@ -728,9 +784,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
// 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) {
|
||||||
mName.setText(mainUserId.name);
|
mCollapsingToolbarLayout.setTitle(mainUserId.name);
|
||||||
} else {
|
} else {
|
||||||
mName.setText(R.string.user_id_no_name);
|
mCollapsingToolbarLayout.setTitle(getString(R.string.user_id_no_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||||
@@ -767,8 +823,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void onPostExecute(Bitmap photo) {
|
protected void onPostExecute(Bitmap photo) {
|
||||||
|
if (photo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mPhoto.setImageBitmap(photo);
|
mPhoto.setImageBitmap(photo);
|
||||||
mPhoto.setVisibility(View.VISIBLE);
|
mPhotoLayout.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -781,9 +841,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
State.REVOKED, R.color.icons, true);
|
State.REVOKED, R.color.icons, true);
|
||||||
color = getResources().getColor(R.color.key_flag_red);
|
color = getResources().getColor(R.color.key_flag_red);
|
||||||
|
|
||||||
mActionEncryptFile.setVisibility(View.GONE);
|
mActionEncryptFile.setVisibility(View.INVISIBLE);
|
||||||
mActionEncryptText.setVisibility(View.GONE);
|
mActionEncryptText.setVisibility(View.INVISIBLE);
|
||||||
mActionNfc.setVisibility(View.GONE);
|
mActionNfc.setVisibility(View.INVISIBLE);
|
||||||
mFab.setVisibility(View.GONE);
|
mFab.setVisibility(View.GONE);
|
||||||
mQrCodeLayout.setVisibility(View.GONE);
|
mQrCodeLayout.setVisibility(View.GONE);
|
||||||
} else if (mIsExpired) {
|
} else if (mIsExpired) {
|
||||||
@@ -797,9 +857,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
State.EXPIRED, R.color.icons, true);
|
State.EXPIRED, R.color.icons, true);
|
||||||
color = getResources().getColor(R.color.key_flag_red);
|
color = getResources().getColor(R.color.key_flag_red);
|
||||||
|
|
||||||
mActionEncryptFile.setVisibility(View.GONE);
|
mActionEncryptFile.setVisibility(View.INVISIBLE);
|
||||||
mActionEncryptText.setVisibility(View.GONE);
|
mActionEncryptText.setVisibility(View.INVISIBLE);
|
||||||
mActionNfc.setVisibility(View.GONE);
|
mActionNfc.setVisibility(View.INVISIBLE);
|
||||||
mFab.setVisibility(View.GONE);
|
mFab.setVisibility(View.GONE);
|
||||||
mQrCodeLayout.setVisibility(View.GONE);
|
mQrCodeLayout.setVisibility(View.GONE);
|
||||||
} else if (mIsSecret) {
|
} else if (mIsSecret) {
|
||||||
@@ -814,15 +874,15 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
mQrCodeLayout.setVisibility(View.VISIBLE);
|
mQrCodeLayout.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// and place leftOf qr code
|
// and place leftOf qr code
|
||||||
RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
|
// RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
|
||||||
mName.getLayoutParams();
|
// mName.getLayoutParams();
|
||||||
// remove right margin
|
// // remove right margin
|
||||||
nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
|
// nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
|
||||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
// if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
nameParams.setMarginEnd(0);
|
// nameParams.setMarginEnd(0);
|
||||||
}
|
// }
|
||||||
nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
|
// nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
|
||||||
mName.setLayoutParams(nameParams);
|
// mName.setLayoutParams(nameParams);
|
||||||
|
|
||||||
RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
|
RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
|
||||||
mStatusText.getLayoutParams();
|
mStatusText.getLayoutParams();
|
||||||
@@ -844,7 +904,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
mFab.setVisibility(View.VISIBLE);
|
mFab.setVisibility(View.VISIBLE);
|
||||||
// noinspection deprecation (no getDrawable with theme at current minApi level 15!)
|
// noinspection deprecation (no getDrawable with theme at current minApi level 15!)
|
||||||
mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
|
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
|
||||||
} else {
|
} else {
|
||||||
mActionEncryptFile.setVisibility(View.VISIBLE);
|
mActionEncryptFile.setVisibility(View.VISIBLE);
|
||||||
mActionEncryptText.setVisibility(View.VISIBLE);
|
mActionEncryptText.setVisibility(View.VISIBLE);
|
||||||
@@ -872,22 +932,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mPreviousColor == 0 || mPreviousColor == color) {
|
if (mPreviousColor == 0 || mPreviousColor == color) {
|
||||||
mStatusBar.setBackgroundColor(getStatusBarBackgroundColor(color));
|
mAppBarLayout.setBackgroundColor(color);
|
||||||
mBigToolbar.setBackgroundColor(color);
|
mCollapsingToolbarLayout.setContentScrimColor(color);
|
||||||
|
mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color));
|
||||||
mPreviousColor = color;
|
mPreviousColor = color;
|
||||||
} else {
|
} else {
|
||||||
ObjectAnimator colorFade1 =
|
ObjectAnimator colorFade =
|
||||||
ObjectAnimator.ofObject(mStatusBar, "backgroundColor",
|
ObjectAnimator.ofObject(mAppBarLayout, "backgroundColor",
|
||||||
new ArgbEvaluator(), mPreviousColor,
|
|
||||||
getStatusBarBackgroundColor(color));
|
|
||||||
ObjectAnimator colorFade2 =
|
|
||||||
ObjectAnimator.ofObject(mBigToolbar, "backgroundColor",
|
|
||||||
new ArgbEvaluator(), mPreviousColor, color);
|
new ArgbEvaluator(), mPreviousColor, color);
|
||||||
|
mCollapsingToolbarLayout.setContentScrimColor(color);
|
||||||
|
mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color));
|
||||||
|
|
||||||
colorFade1.setDuration(1200);
|
colorFade.setDuration(1200);
|
||||||
colorFade2.setDuration(1200);
|
colorFade.start();
|
||||||
colorFade1.start();
|
|
||||||
colorFade2.start();
|
|
||||||
mPreviousColor = color;
|
mPreviousColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -963,4 +1020,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
|
|||||||
public static final int TAB_IDENTITIES = 1;
|
public static final int TAB_IDENTITIES = 1;
|
||||||
public static final int TAB_SUBKEYS = 2;
|
public static final int TAB_SUBKEYS = 2;
|
||||||
public static final int TAB_CERTS = 3;
|
public static final int TAB_CERTS = 3;
|
||||||
public static final int TAB_KEYBASE = 4;
|
|
||||||
|
|
||||||
// view
|
// view
|
||||||
private ViewPager mViewPager;
|
private ViewPager mViewPager;
|
||||||
@@ -140,11 +139,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
|
|||||||
adapter.addTab(ViewKeyAdvCertsFragment.class,
|
adapter.addTab(ViewKeyAdvCertsFragment.class,
|
||||||
certsBundle, getString(R.string.key_view_tab_certs));
|
certsBundle, getString(R.string.key_view_tab_certs));
|
||||||
|
|
||||||
Bundle trustBundle = new Bundle();
|
|
||||||
trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri);
|
|
||||||
adapter.addTab(ViewKeyTrustFragment.class,
|
|
||||||
trustBundle, getString(R.string.key_view_tab_keybase));
|
|
||||||
|
|
||||||
// update layout after operations
|
// update layout after operations
|
||||||
mSlidingTabLayout.setViewPager(mViewPager);
|
mSlidingTabLayout.setViewPager(mViewPager);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements
|
|||||||
case LOADER_ID_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
||||||
return new CursorLoader(getActivity(), baseUri,
|
return new CursorLoader(getActivity(), baseUri,
|
||||||
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
|
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -18,52 +18,73 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
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.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.Transition;
|
||||||
|
import android.transition.TransitionInflater;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.*;
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
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.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||||
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.OnIdentityLoadedListener;
|
||||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ViewKeyFragment extends LoaderFragment implements
|
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";
|
||||||
|
public static final String ARG_POSTPONE_TYPE = "postpone_type";
|
||||||
|
|
||||||
private ListView mUserIds;
|
private ListView mUserIds;
|
||||||
//private ListView mLinkedSystemContact;
|
//private ListView mLinkedSystemContact;
|
||||||
|
|
||||||
boolean mIsSecret = false;
|
enum PostponeType {
|
||||||
|
NONE, LINKED;
|
||||||
|
}
|
||||||
|
|
||||||
CardView mSystemContactCard;
|
boolean mIsSecret = false;
|
||||||
LinearLayout mSystemContactLayout;
|
|
||||||
ImageView mSystemContactPicture;
|
|
||||||
TextView mSystemContactName;
|
|
||||||
|
|
||||||
private static final int LOADER_ID_UNIFIED = 0;
|
private static final int LOADER_ID_UNIFIED = 0;
|
||||||
private static final int LOADER_ID_USER_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_CONTACT = 2;
|
||||||
|
private static final int LOADER_ID_LINKED_IDS = 3;
|
||||||
|
|
||||||
private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
|
private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
|
||||||
= "loader_linked_contact_master_key_id";
|
= "loader_linked_contact_master_key_id";
|
||||||
@@ -71,16 +92,29 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
= "loader_linked_contact_is_secret";
|
= "loader_linked_contact_is_secret";
|
||||||
|
|
||||||
private UserIdsAdapter mUserIdsAdapter;
|
private UserIdsAdapter mUserIdsAdapter;
|
||||||
|
private LinkedIdsAdapter mLinkedIdsAdapter;
|
||||||
|
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
private PostponeType mPostponeType;
|
||||||
|
|
||||||
|
private CardView mSystemContactCard;
|
||||||
|
private LinearLayout mSystemContactLayout;
|
||||||
|
private ImageView mSystemContactPicture;
|
||||||
|
private TextView mSystemContactName;
|
||||||
|
|
||||||
|
private ListView mLinkedIds;
|
||||||
|
private CardView mLinkedIdsCard;
|
||||||
|
private byte[] mFingerprint;
|
||||||
|
private TextView mLinkedIdsExpander;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
public static ViewKeyFragment newInstance(Uri dataUri) {
|
public static ViewKeyFragment newInstance(Uri dataUri, PostponeType postponeType) {
|
||||||
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.putInt(ARG_POSTPONE_TYPE, postponeType.ordinal());
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
@@ -93,6 +127,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
View view = inflater.inflate(R.layout.view_key_fragment, getContainer());
|
View view = inflater.inflate(R.layout.view_key_fragment, getContainer());
|
||||||
|
|
||||||
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
||||||
|
mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids);
|
||||||
|
|
||||||
|
mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids);
|
||||||
|
|
||||||
|
mLinkedIdsExpander = (TextView) view.findViewById(R.id.view_key_linked_ids_expander);
|
||||||
|
|
||||||
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -100,6 +139,12 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
showUserIdInfo(position);
|
showUserIdInfo(position);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mLinkedIds.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
showLinkedId(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card);
|
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);
|
||||||
@@ -109,6 +154,47 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showLinkedId(final int position) {
|
||||||
|
final LinkedIdViewFragment frag;
|
||||||
|
try {
|
||||||
|
frag = mLinkedIdsAdapter.getLinkedIdFragment(mDataUri, position, mFingerprint);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
Transition trans = TransitionInflater.from(getActivity())
|
||||||
|
.inflateTransition(R.transition.linked_id_card_trans);
|
||||||
|
// setSharedElementReturnTransition(trans);
|
||||||
|
setExitTransition(new Fade());
|
||||||
|
frag.setSharedElementEnterTransition(trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.view_key_fragment, frag)
|
||||||
|
.hide(frag)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
frag.setOnIdentityLoadedListener(new OnIdentityLoadedListener() {
|
||||||
|
@Override
|
||||||
|
public void onIdentityLoaded() {
|
||||||
|
new Handler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.show(frag)
|
||||||
|
.addSharedElement(mLinkedIdsCard, "card_linked_ids")
|
||||||
|
.remove(ViewKeyFragment.this)
|
||||||
|
.addToBackStack("linked_id")
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void showUserIdInfo(final int position) {
|
private void showUserIdInfo(final int position) {
|
||||||
if (!mIsSecret) {
|
if (!mIsSecret) {
|
||||||
final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position);
|
final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position);
|
||||||
@@ -129,8 +215,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
* Hides card if no linked system contact exists. 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" (own profile) contact details are loaded.
|
* In the case of a secret key, "me" (own profile) contact details are loaded.
|
||||||
*
|
|
||||||
* @param contactId
|
|
||||||
*/
|
*/
|
||||||
private void loadLinkedSystemContact(final long contactId) {
|
private void loadLinkedSystemContact(final long contactId) {
|
||||||
// contact doesn't exist, stop
|
// contact doesn't exist, stop
|
||||||
@@ -188,7 +272,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
* 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);
|
||||||
@@ -202,6 +285,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||||
|
mPostponeType = PostponeType.values()[getArguments().getInt(ARG_POSTPONE_TYPE, 0)];
|
||||||
if (dataUri == null) {
|
if (dataUri == null) {
|
||||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
@@ -225,12 +309,17 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
};
|
};
|
||||||
|
|
||||||
static final int INDEX_MASTER_KEY_ID = 1;
|
static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
static final int INDEX_USER_ID = 2;
|
static final int INDEX_USER_ID = 2;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
static final int INDEX_IS_REVOKED = 3;
|
static final int INDEX_IS_REVOKED = 3;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
static final int INDEX_IS_EXPIRED = 4;
|
static final int INDEX_IS_EXPIRED = 4;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
static final int INDEX_VERIFIED = 5;
|
static final int INDEX_VERIFIED = 5;
|
||||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
static final int INDEX_HAS_ANY_SECRET = 6;
|
||||||
static final int INDEX_FINGERPRINT = 7;
|
static final int INDEX_FINGERPRINT = 7;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
static final int INDEX_HAS_ENCRYPT = 8;
|
static final int INDEX_HAS_ENCRYPT = 8;
|
||||||
|
|
||||||
private static final String[] RAWCONTACT_PROJECTION = {
|
private static final String[] RAWCONTACT_PROJECTION = {
|
||||||
@@ -246,21 +335,26 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
// Prepare the loaders. Either re-connect with an existing ones,
|
// Prepare the loaders. Either re-connect with an existing ones,
|
||||||
// or start new ones.
|
// or start new ones.
|
||||||
// TODO Is this loader the same as the one in the activity?
|
|
||||||
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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_UNIFIED: {
|
case LOADER_ID_UNIFIED: {
|
||||||
|
setContentShown(false, false);
|
||||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||||
}
|
}
|
||||||
case LOADER_ID_USER_IDS:
|
|
||||||
|
case LOADER_ID_USER_IDS: {
|
||||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
case LOADER_ID_LINKED_IDS: {
|
||||||
|
return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||||
|
}
|
||||||
|
|
||||||
//we need a separate loader for linked contact to ensure refreshing on verification
|
//we need a separate loader for linked contact to ensure refreshing on verification
|
||||||
case LOADER_ID_LINKED_CONTACT: {
|
case LOADER_ID_LINKED_CONTACT: {
|
||||||
@@ -310,19 +404,21 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
if (data.moveToFirst()) {
|
if (data.moveToFirst()) {
|
||||||
|
|
||||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||||
|
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||||
|
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||||
|
|
||||||
// load user ids after we know if it's a secret key
|
// load user ids after we know if it's a secret key
|
||||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||||
mUserIds.setAdapter(mUserIdsAdapter);
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||||
|
|
||||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
if (Preferences.getPreferences(getActivity()).getExperimentalEnableLinkedIdentities()) {
|
||||||
// we need to load linked contact here to prevent lag introduced by loader
|
mLinkedIdsAdapter =
|
||||||
// for the linked contact
|
new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander);
|
||||||
long contactId = ContactHelper.findContactId(
|
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||||
getActivity().getContentResolver(),
|
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
||||||
masterKeyId);
|
}
|
||||||
loadLinkedSystemContact(contactId);
|
|
||||||
|
|
||||||
Bundle linkedContactData = new Bundle();
|
Bundle linkedContactData = new Bundle();
|
||||||
linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
|
linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
|
||||||
@@ -336,10 +432,28 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
case LOADER_ID_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
|
setContentShown(true, false);
|
||||||
mUserIdsAdapter.swapCursor(data);
|
mUserIdsAdapter.swapCursor(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case LOADER_ID_LINKED_IDS: {
|
||||||
|
mLinkedIdsAdapter.swapCursor(data);
|
||||||
|
mLinkedIdsCard.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mPostponeType == PostponeType.LINKED) {
|
||||||
|
mLinkedIdsCard.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
|
||||||
|
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw() {
|
||||||
|
mLinkedIdsCard.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
getActivity().startPostponedEnterTransition();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case LOADER_ID_LINKED_CONTACT: {
|
case LOADER_ID_LINKED_CONTACT: {
|
||||||
if (data.moveToFirst()) {// if we have a linked contact
|
if (data.moveToFirst()) {// if we have a linked contact
|
||||||
long contactId = data.getLong(INDEX_CONTACT_ID);
|
long contactId = data.getLong(INDEX_CONTACT_ID);
|
||||||
@@ -349,7 +463,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
setContentShown(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,6 +476,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
mUserIdsAdapter.swapCursor(null);
|
mUserIdsAdapter.swapCursor(null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case LOADER_ID_LINKED_IDS: {
|
||||||
|
mLinkedIdsCard.setVisibility(View.GONE);
|
||||||
|
mLinkedIdsAdapter.swapCursor(null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,14 +59,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ViewKeyTrustFragment extends LoaderFragment implements
|
public class ViewKeyKeybaseFragment extends LoaderFragment implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor>,
|
LoaderManager.LoaderCallbacks<Cursor>,
|
||||||
CryptoOperationHelper.Callback<KeybaseVerificationParcel, KeybaseVerificationResult> {
|
CryptoOperationHelper.Callback<KeybaseVerificationParcel, KeybaseVerificationResult> {
|
||||||
|
|
||||||
public static final String ARG_DATA_URI = "uri";
|
public static final String ARG_DATA_URI = "uri";
|
||||||
|
|
||||||
private View mStartSearch;
|
|
||||||
private TextView mTrustReadout;
|
|
||||||
private TextView mReportHeader;
|
private TextView mReportHeader;
|
||||||
private TableLayout mProofListing;
|
private TableLayout mProofListing;
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
@@ -86,15 +84,25 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
private CryptoOperationHelper<KeybaseVerificationParcel, KeybaseVerificationResult>
|
private CryptoOperationHelper<KeybaseVerificationParcel, KeybaseVerificationResult>
|
||||||
mKeybaseOpHelper;
|
mKeybaseOpHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new instance of this fragment
|
||||||
|
*/
|
||||||
|
public static ViewKeyKeybaseFragment newInstance(Uri dataUri) {
|
||||||
|
ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||||
|
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
@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);
|
||||||
View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer());
|
View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer());
|
||||||
mInflater = inflater;
|
mInflater = inflater;
|
||||||
|
|
||||||
mTrustReadout = (TextView) view.findViewById(R.id.view_key_trust_readout);
|
|
||||||
mStartSearch = view.findViewById(R.id.view_key_trust_search_cloud);
|
|
||||||
mStartSearch.setEnabled(false);
|
|
||||||
mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative);
|
mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative);
|
||||||
mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list);
|
mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list);
|
||||||
mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header);
|
mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header);
|
||||||
@@ -157,85 +165,47 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean nothingSpecial = true;
|
boolean nothingSpecial = true;
|
||||||
StringBuilder message = new StringBuilder();
|
|
||||||
|
|
||||||
// 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.)
|
||||||
if (data.moveToFirst()) {
|
if (data.moveToFirst()) {
|
||||||
|
|
||||||
if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) {
|
|
||||||
message.append(getString(R.string.key_trust_it_is_yours)).append("\n");
|
|
||||||
nothingSpecial = false;
|
|
||||||
} else if (data.getInt(INDEX_VERIFIED) != 0) {
|
|
||||||
message.append(getString(R.string.key_trust_already_verified)).append("\n");
|
|
||||||
nothingSpecial = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this key is revoked, don’t trust it!
|
|
||||||
if (data.getInt(INDEX_TRUST_IS_REVOKED) != 0) {
|
|
||||||
message.append(getString(R.string.key_trust_revoked)).
|
|
||||||
append(getString(R.string.key_trust_old_keys));
|
|
||||||
|
|
||||||
nothingSpecial = false;
|
|
||||||
} else {
|
|
||||||
if (data.getInt(INDEX_TRUST_IS_EXPIRED) != 0) {
|
|
||||||
|
|
||||||
// if expired, don’t trust it!
|
|
||||||
message.append(getString(R.string.key_trust_expired)).
|
|
||||||
append(getString(R.string.key_trust_old_keys));
|
|
||||||
|
|
||||||
nothingSpecial = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nothingSpecial) {
|
|
||||||
message.append(getString(R.string.key_trust_maybe));
|
|
||||||
}
|
|
||||||
|
|
||||||
final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT);
|
final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT);
|
||||||
final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
|
final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
|
||||||
if (fingerprint != null) {
|
|
||||||
|
|
||||||
mStartSearch.setEnabled(true);
|
startSearch(fingerprint);
|
||||||
mStartSearch.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
final Preferences.ProxyPrefs proxyPrefs =
|
|
||||||
Preferences.getPreferences(getActivity()).getProxyPrefs();
|
|
||||||
|
|
||||||
OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
|
|
||||||
@Override
|
|
||||||
public void onOrbotStarted() {
|
|
||||||
mStartSearch.setEnabled(false);
|
|
||||||
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNeutralButton() {
|
|
||||||
mStartSearch.setEnabled(false);
|
|
||||||
new DescribeKey(ParcelableProxy.getForNoProxy())
|
|
||||||
.execute(fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
|
|
||||||
mStartSearch.setEnabled(false);
|
|
||||||
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mTrustReadout.setText(message);
|
|
||||||
setContentShown(true);
|
setContentShown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startSearch(final String fingerprint) {
|
||||||
|
final Preferences.ProxyPrefs proxyPrefs =
|
||||||
|
Preferences.getPreferences(getActivity()).getProxyPrefs();
|
||||||
|
|
||||||
|
OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
|
||||||
|
@Override
|
||||||
|
public void onOrbotStarted() {
|
||||||
|
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNeutralButton() {
|
||||||
|
new DescribeKey(ParcelableProxy.getForNoProxy())
|
||||||
|
.execute(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
|
||||||
|
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
||||||
* We need to make sure we are no longer using it.
|
* We need to make sure we are no longer using it.
|
||||||
@@ -299,17 +269,16 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
return new ResultPage(getString(R.string.key_trust_results_prefix), proofList);
|
return new ResultPage(getString(R.string.key_trust_results_prefix), proofList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks,String proofType){
|
private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) {
|
||||||
//Formatting SpannableStringBuilder with String.format() causes the links to stop working.
|
//Formatting SpannableStringBuilder with String.format() causes the links to stop working.
|
||||||
//This method is to insert the links while reserving the links
|
//This method is to insert the links while reserving the links
|
||||||
|
|
||||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||||
ssb.append(proofType);
|
ssb.append(proofType);
|
||||||
if(proofType.contains("%s")){
|
if (proofType.contains("%s")) {
|
||||||
int i = proofType.indexOf("%s");
|
int i = proofType.indexOf("%s");
|
||||||
ssb.replace(i,i+2,proofLinks);
|
ssb.replace(i, i + 2, proofLinks);
|
||||||
}
|
} else ssb.append(proofLinks);
|
||||||
else ssb.append(proofLinks);
|
|
||||||
|
|
||||||
return ssb;
|
return ssb;
|
||||||
}
|
}
|
||||||
@@ -343,7 +312,6 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence);
|
result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence);
|
||||||
}
|
}
|
||||||
|
|
||||||
mStartSearch.setVisibility(View.GONE);
|
|
||||||
mReportHeader.setVisibility(View.VISIBLE);
|
mReportHeader.setVisibility(View.VISIBLE);
|
||||||
mProofListing.setVisibility(View.VISIBLE);
|
mProofListing.setVisibility(View.VISIBLE);
|
||||||
mReportHeader.setText(result.mHeader);
|
mReportHeader.setText(result.mHeader);
|
||||||
@@ -358,22 +326,35 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
text.setMovementMethod(LinkMovementMethod.getInstance());
|
text.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
mProofListing.addView(row);
|
mProofListing.addView(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mSearchReport.loadDataWithBaseURL("file:///android_res/drawable/", s, "text/html", "UTF-8", null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getProofNarrative(int proofType) {
|
private String getProofNarrative(int proofType) {
|
||||||
int stringIndex;
|
int stringIndex;
|
||||||
switch (proofType) {
|
switch (proofType) {
|
||||||
case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break;
|
case Proof.PROOF_TYPE_TWITTER:
|
||||||
case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break;
|
stringIndex = R.string.keybase_narrative_twitter;
|
||||||
case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break;
|
break;
|
||||||
case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break;
|
case Proof.PROOF_TYPE_GITHUB:
|
||||||
case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break;
|
stringIndex = R.string.keybase_narrative_github;
|
||||||
case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break;
|
break;
|
||||||
case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break;
|
case Proof.PROOF_TYPE_DNS:
|
||||||
default: stringIndex = R.string.keybase_narrative_unknown;
|
stringIndex = R.string.keybase_narrative_dns;
|
||||||
|
break;
|
||||||
|
case Proof.PROOF_TYPE_WEB_SITE:
|
||||||
|
stringIndex = R.string.keybase_narrative_web_site;
|
||||||
|
break;
|
||||||
|
case Proof.PROOF_TYPE_HACKERNEWS:
|
||||||
|
stringIndex = R.string.keybase_narrative_hackernews;
|
||||||
|
break;
|
||||||
|
case Proof.PROOF_TYPE_COINBASE:
|
||||||
|
stringIndex = R.string.keybase_narrative_coinbase;
|
||||||
|
break;
|
||||||
|
case Proof.PROOF_TYPE_REDDIT:
|
||||||
|
stringIndex = R.string.keybase_narrative_reddit;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
stringIndex = R.string.keybase_narrative_unknown;
|
||||||
}
|
}
|
||||||
return getActivity().getString(stringIndex);
|
return getActivity().getString(stringIndex);
|
||||||
}
|
}
|
||||||
@@ -390,14 +371,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
// which proofs do we have working verifiers for?
|
// which proofs do we have working verifiers for?
|
||||||
private boolean haveProofFor(int proofType) {
|
private boolean haveProofFor(int proofType) {
|
||||||
switch (proofType) {
|
switch (proofType) {
|
||||||
case Proof.PROOF_TYPE_TWITTER: return true;
|
case Proof.PROOF_TYPE_TWITTER:
|
||||||
case Proof.PROOF_TYPE_GITHUB: return true;
|
return true;
|
||||||
case Proof.PROOF_TYPE_DNS: return true;
|
case Proof.PROOF_TYPE_GITHUB:
|
||||||
case Proof.PROOF_TYPE_WEB_SITE: return true;
|
return true;
|
||||||
case Proof.PROOF_TYPE_HACKERNEWS: return true;
|
case Proof.PROOF_TYPE_DNS:
|
||||||
case Proof.PROOF_TYPE_COINBASE: return true;
|
return true;
|
||||||
case Proof.PROOF_TYPE_REDDIT: return true;
|
case Proof.PROOF_TYPE_WEB_SITE:
|
||||||
default: return false;
|
return true;
|
||||||
|
case Proof.PROOF_TYPE_HACKERNEWS:
|
||||||
|
return true;
|
||||||
|
case Proof.PROOF_TYPE_COINBASE:
|
||||||
|
return true;
|
||||||
|
case Proof.PROOF_TYPE_REDDIT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@@ -193,9 +194,9 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
String dateTime = DateUtils.formatDateTime(context,
|
String dateTime = DateUtils.formatDateTime(context,
|
||||||
item.mCreation.getTime(),
|
item.mCreation.getTime(),
|
||||||
DateUtils.FORMAT_SHOW_DATE
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
|
| DateUtils.FORMAT_SHOW_TIME
|
||||||
| DateUtils.FORMAT_SHOW_YEAR
|
| DateUtils.FORMAT_SHOW_YEAR
|
||||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||||
|
|
||||||
mCreationDate.setText(context.getString(R.string.label_key_created,
|
mCreationDate.setText(context.getString(R.string.label_key_created,
|
||||||
dateTime));
|
dateTime));
|
||||||
mCreationDate.setTextColor(textColor);
|
mCreationDate.setTextColor(textColor);
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014-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.adapter;
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||||
|
import org.sufficientlysecure.keychain.util.FilterCursorWrapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class LinkedIdsAdapter extends UserAttributesAdapter {
|
||||||
|
private final boolean mIsSecret;
|
||||||
|
protected LayoutInflater mInflater;
|
||||||
|
WeakHashMap<Integer,UriAttribute> mLinkedIdentityCache = new WeakHashMap<>();
|
||||||
|
|
||||||
|
private Cursor mUnfilteredCursor;
|
||||||
|
|
||||||
|
private TextView mExpander;
|
||||||
|
|
||||||
|
public LinkedIdsAdapter(Context context, Cursor c, int flags,
|
||||||
|
boolean isSecret, TextView expander) {
|
||||||
|
super(context, c, flags);
|
||||||
|
mInflater = LayoutInflater.from(context);
|
||||||
|
mIsSecret = isSecret;
|
||||||
|
|
||||||
|
if (expander != null) {
|
||||||
|
expander.setVisibility(View.GONE);
|
||||||
|
/* don't show an expander (maybe in some sort of advanced view?)
|
||||||
|
mExpander = expander;
|
||||||
|
mExpander.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
showUnfiltered();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor swapCursor(Cursor cursor) {
|
||||||
|
if (cursor == null) {
|
||||||
|
mUnfilteredCursor = null;
|
||||||
|
return super.swapCursor(null);
|
||||||
|
}
|
||||||
|
mUnfilteredCursor = cursor;
|
||||||
|
FilterCursorWrapper filteredCursor = new FilterCursorWrapper(cursor) {
|
||||||
|
@Override
|
||||||
|
public boolean isVisible(Cursor cursor) {
|
||||||
|
UriAttribute id = getItemAtPosition(cursor);
|
||||||
|
return id instanceof LinkedAttribute;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mExpander != null) {
|
||||||
|
int hidden = filteredCursor.getHiddenCount();
|
||||||
|
if (hidden == 0) {
|
||||||
|
mExpander.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
mExpander.setVisibility(View.VISIBLE);
|
||||||
|
mExpander.setText(mContext.getResources().getQuantityString(
|
||||||
|
R.plurals.linked_id_expand, hidden));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.swapCursor(filteredCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showUnfiltered() {
|
||||||
|
mExpander.setVisibility(View.GONE);
|
||||||
|
super.swapCursor(mUnfilteredCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
|
||||||
|
ViewHolder holder = (ViewHolder) view.getTag();
|
||||||
|
|
||||||
|
if (!mIsSecret) {
|
||||||
|
int isVerified = cursor.getInt(INDEX_VERIFIED);
|
||||||
|
switch (isVerified) {
|
||||||
|
case Certs.VERIFIED_SECRET:
|
||||||
|
KeyFormattingUtils.setStatusImage(mContext, holder.vVerified,
|
||||||
|
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||||
|
break;
|
||||||
|
case Certs.VERIFIED_SELF:
|
||||||
|
KeyFormattingUtils.setStatusImage(mContext, holder.vVerified,
|
||||||
|
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
KeyFormattingUtils.setStatusImage(mContext, holder.vVerified,
|
||||||
|
null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UriAttribute id = getItemAtPosition(cursor);
|
||||||
|
holder.setData(mContext, id);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
view.setTransitionName(id.mUri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public UriAttribute getItemAtPosition(Cursor cursor) {
|
||||||
|
int rank = cursor.getInt(INDEX_RANK);
|
||||||
|
Log.d(Constants.TAG, "requested rank: " + rank);
|
||||||
|
|
||||||
|
UriAttribute ret = mLinkedIdentityCache.get(rank);
|
||||||
|
if (ret != null) {
|
||||||
|
Log.d(Constants.TAG, "cached!");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "not cached!");
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] data = cursor.getBlob(INDEX_ATTRIBUTE_DATA);
|
||||||
|
ret = LinkedAttribute.fromAttributeData(data);
|
||||||
|
mLinkedIdentityCache.put(rank, ret);
|
||||||
|
return ret;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "could not read linked identity subpacket data", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UriAttribute getItem(int position) {
|
||||||
|
Cursor cursor = getCursor();
|
||||||
|
cursor.moveToPosition(position);
|
||||||
|
return getItemAtPosition(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
View v = mInflater.inflate(R.layout.linked_id_item, null);
|
||||||
|
ViewHolder holder = new ViewHolder(v);
|
||||||
|
v.setTag(holder);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't show revoked user ids, irrelevant for average users
|
||||||
|
public static final String LINKED_IDS_WHERE = UserPackets.IS_REVOKED + " = 0";
|
||||||
|
|
||||||
|
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||||
|
Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri);
|
||||||
|
return new CursorLoader(activity, baseUri,
|
||||||
|
UserIdsAdapter.USER_PACKETS_PROJECTION, LINKED_IDS_WHERE, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedIdViewFragment getLinkedIdFragment(Uri baseUri,
|
||||||
|
int position, byte[] fingerprint) throws IOException {
|
||||||
|
Cursor c = getCursor();
|
||||||
|
c.moveToPosition(position);
|
||||||
|
int rank = c.getInt(UserIdsAdapter.INDEX_RANK);
|
||||||
|
|
||||||
|
Uri dataUri = UserPackets.buildLinkedIdsUri(baseUri);
|
||||||
|
return LinkedIdViewFragment.newInstance(dataUri, rank, mIsSecret, fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder {
|
||||||
|
final public ImageView vVerified;
|
||||||
|
final public ImageView vIcon;
|
||||||
|
final public TextView vTitle;
|
||||||
|
final public TextView vComment;
|
||||||
|
|
||||||
|
public ViewHolder(View view) {
|
||||||
|
vVerified = (ImageView) view.findViewById(R.id.linked_id_certified_icon);
|
||||||
|
vIcon = (ImageView) view.findViewById(R.id.linked_id_type_icon);
|
||||||
|
vTitle = (TextView) view.findViewById(R.id.linked_id_title);
|
||||||
|
vComment = (TextView) view.findViewById(R.id.linked_id_comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(Context context, UriAttribute id) {
|
||||||
|
|
||||||
|
vTitle.setText(id.getDisplayTitle(context));
|
||||||
|
|
||||||
|
String comment = id.getDisplayComment(context);
|
||||||
|
if (comment != null) {
|
||||||
|
vComment.setVisibility(View.VISIBLE);
|
||||||
|
vComment.setText(comment);
|
||||||
|
} else {
|
||||||
|
vComment.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
vIcon.setImageResource(id.getDisplayIcon());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekAttention() {
|
||||||
|
ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
|
||||||
|
anim.setStartDelay(200);
|
||||||
|
anim.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CursorAdapter;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
|
||||||
|
|
||||||
|
public class LinkedIdsCertAdapter extends CursorAdapter {
|
||||||
|
|
||||||
|
public static final String[] USER_CERTS_PROJECTION = new String[]{
|
||||||
|
UserPackets._ID,
|
||||||
|
UserPackets.TYPE,
|
||||||
|
UserPackets.USER_ID,
|
||||||
|
UserPackets.ATTRIBUTE_DATA,
|
||||||
|
UserPackets.RANK,
|
||||||
|
UserPackets.VERIFIED,
|
||||||
|
UserPackets.IS_PRIMARY,
|
||||||
|
UserPackets.IS_REVOKED
|
||||||
|
};
|
||||||
|
protected static final int INDEX_ID = 0;
|
||||||
|
protected static final int INDEX_TYPE = 1;
|
||||||
|
protected static final int INDEX_USER_ID = 2;
|
||||||
|
protected static final int INDEX_ATTRIBUTE_DATA = 3;
|
||||||
|
protected static final int INDEX_RANK = 4;
|
||||||
|
protected static final int INDEX_VERIFIED = 5;
|
||||||
|
protected static final int INDEX_IS_PRIMARY = 6;
|
||||||
|
protected static final int INDEX_IS_REVOKED = 7;
|
||||||
|
|
||||||
|
public LinkedIdsCertAdapter(Context context, Cursor c, int flags) {
|
||||||
|
super(context, c, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||||
|
Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri);
|
||||||
|
return new CursorLoader(activity, baseUri,
|
||||||
|
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -171,7 +171,7 @@ public class MultiUserIdsAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
CertifyAction action = actions.get(keyId);
|
CertifyAction action = actions.get(keyId);
|
||||||
if (actions.get(keyId) == null) {
|
if (actions.get(keyId) == null) {
|
||||||
actions.put(keyId, new CertifyAction(keyId, uids));
|
actions.put(keyId, new CertifyAction(keyId, uids, null));
|
||||||
} else {
|
} else {
|
||||||
action.mUserIds.addAll(uids);
|
action.mUserIds.addAll(uids);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,9 +137,9 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
|
|||||||
String dateTime = DateUtils.formatDateTime(context,
|
String dateTime = DateUtils.formatDateTime(context,
|
||||||
cursor.getLong(mIndexCreation) * 1000,
|
cursor.getLong(mIndexCreation) * 1000,
|
||||||
DateUtils.FORMAT_SHOW_DATE
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
|
| DateUtils.FORMAT_SHOW_TIME
|
||||||
| DateUtils.FORMAT_SHOW_YEAR
|
| DateUtils.FORMAT_SHOW_YEAR
|
||||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||||
|
|
||||||
h.creation.setText(context.getString(R.string.label_key_created, dateTime));
|
h.creation.setText(context.getString(R.string.label_key_created, dateTime));
|
||||||
h.creation.setVisibility(View.VISIBLE);
|
h.creation.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,22 +8,24 @@ import android.view.View;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
|
||||||
public abstract class UserAttributesAdapter extends CursorAdapter {
|
public abstract class UserAttributesAdapter extends CursorAdapter {
|
||||||
public static final String[] USER_IDS_PROJECTION = new String[]{
|
public static final String[] USER_PACKETS_PROJECTION = new String[]{
|
||||||
UserPackets._ID,
|
UserPackets._ID,
|
||||||
UserPackets.TYPE,
|
UserPackets.TYPE,
|
||||||
UserPackets.USER_ID,
|
UserPackets.USER_ID,
|
||||||
|
UserPackets.ATTRIBUTE_DATA,
|
||||||
UserPackets.RANK,
|
UserPackets.RANK,
|
||||||
UserPackets.VERIFIED,
|
UserPackets.VERIFIED,
|
||||||
UserPackets.IS_PRIMARY,
|
UserPackets.IS_PRIMARY,
|
||||||
UserPackets.IS_REVOKED
|
UserPackets.IS_REVOKED
|
||||||
};
|
};
|
||||||
protected static final int INDEX_ID = 0;
|
public static final int INDEX_ID = 0;
|
||||||
protected static final int INDEX_TYPE = 1;
|
public static final int INDEX_TYPE = 1;
|
||||||
protected static final int INDEX_USER_ID = 2;
|
public static final int INDEX_USER_ID = 2;
|
||||||
protected static final int INDEX_RANK = 3;
|
public static final int INDEX_ATTRIBUTE_DATA = 3;
|
||||||
protected static final int INDEX_VERIFIED = 4;
|
public static final int INDEX_RANK = 4;
|
||||||
protected static final int INDEX_IS_PRIMARY = 5;
|
public static final int INDEX_VERIFIED = 5;
|
||||||
protected static final int INDEX_IS_REVOKED = 6;
|
public static final int INDEX_IS_PRIMARY = 6;
|
||||||
|
public static final int INDEX_IS_REVOKED = 7;
|
||||||
|
|
||||||
public UserAttributesAdapter(Context context, Cursor c, int flags) {
|
public UserAttributesAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
@@ -46,4 +48,5 @@ public abstract class UserAttributesAdapter extends CursorAdapter {
|
|||||||
mCursor.moveToPosition(position);
|
mCursor.moveToPosition(position);
|
||||||
return mCursor.getInt(INDEX_VERIFIED);
|
return mCursor.getInt(INDEX_VERIFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ public class UserIdsAdapter extends UserAttributesAdapter {
|
|||||||
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||||
Uri baseUri = UserPackets.buildUserIdsUri(dataUri);
|
Uri baseUri = UserPackets.buildUserIdsUri(dataUri);
|
||||||
return new CursorLoader(activity, baseUri,
|
return new CursorLoader(activity, baseUri,
|
||||||
UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null);
|
UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,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.service.KeyserverSyncAdapterService;
|
||||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +52,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
KeyserverSyncAdapterService.cancelUpdates(this);
|
||||||
|
|
||||||
if (mThemeChanger.changeTheme()) {
|
if (mThemeChanger.changeTheme()) {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.base;
|
package org.sufficientlysecure.keychain.ui.base;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
@@ -50,17 +50,21 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|||||||
* @see KeychainService
|
* @see KeychainService
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
|
public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
|
||||||
extends Fragment implements CryptoOperationHelper.Callback<T, S> {
|
extends Fragment implements CryptoOperationHelper.Callback<T, S> {
|
||||||
|
|
||||||
final private CryptoOperationHelper<T, S> mOperationHelper;
|
final private CryptoOperationHelper<T, S> mOperationHelper;
|
||||||
|
|
||||||
|
public CryptoOperationFragment() {
|
||||||
|
mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
|
||||||
|
}
|
||||||
|
|
||||||
public CryptoOperationFragment(Integer initialProgressMsg) {
|
public CryptoOperationFragment(Integer initialProgressMsg) {
|
||||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg);
|
mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CryptoOperationFragment() {
|
public CryptoOperationFragment(int id, Integer initialProgressMsg) {
|
||||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
|
mOperationHelper = new CryptoOperationHelper<>(id, this, this, initialProgressMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.base;
|
package org.sufficientlysecure.keychain.ui.base;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -70,9 +72,11 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
// particular helper. a request code looks as follows:
|
// particular helper. a request code looks as follows:
|
||||||
// (id << 9) + (1<<8) + REQUEST_CODE_X
|
// (id << 9) + (1<<8) + REQUEST_CODE_X
|
||||||
// that is, starting from LSB, there are 8 bits request code, 1
|
// that is, starting from LSB, there are 8 bits request code, 1
|
||||||
// fixed bit set, then 7 bit operator-id code. the first two
|
// fixed bit set, then 7 bit helper-id code. the first two
|
||||||
// summands are stored in the mId for easy operation.
|
// summands are stored in the mHelperId for easy operation.
|
||||||
private final int mId;
|
private final int mHelperId;
|
||||||
|
// bitmask for helperId is everything except the least 8 bits
|
||||||
|
public static final int HELPER_ID_BITMASK = ~0xff;
|
||||||
|
|
||||||
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
||||||
public static final int REQUEST_CODE_NFC = 2;
|
public static final int REQUEST_CODE_NFC = 2;
|
||||||
@@ -92,7 +96,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
*/
|
*/
|
||||||
public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
|
public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
|
||||||
Integer progressMessageString) {
|
Integer progressMessageString) {
|
||||||
mId = (id << 9) + (1<<8);
|
mHelperId = (id << 9) + (1<<8);
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
mUseFragment = false;
|
mUseFragment = false;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
@@ -103,7 +107,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
* if OperationHelper is being integrated into a fragment
|
* if OperationHelper is being integrated into a fragment
|
||||||
*/
|
*/
|
||||||
public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
|
public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
|
||||||
mId = (id << 9) + (1<<8);
|
mHelperId = (id << 9) + (1<<8);
|
||||||
mFragment = fragment;
|
mFragment = fragment;
|
||||||
mUseFragment = true;
|
mUseFragment = true;
|
||||||
mProgressMessageResource = progressMessageString;
|
mProgressMessageResource = progressMessageString;
|
||||||
@@ -162,9 +166,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
|
|
||||||
protected void startActivityForResult(Intent intent, int requestCode) {
|
protected void startActivityForResult(Intent intent, int requestCode) {
|
||||||
if (mUseFragment) {
|
if (mUseFragment) {
|
||||||
mFragment.startActivityForResult(intent, mId + requestCode);
|
mFragment.startActivityForResult(intent, mHelperId + requestCode);
|
||||||
} else {
|
} else {
|
||||||
mActivity.startActivityForResult(intent, mId + requestCode);
|
mActivity.startActivityForResult(intent, mHelperId + requestCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +180,13 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
Log.d(Constants.TAG, "received activity result in OperationHelper");
|
Log.d(Constants.TAG, "received activity result in OperationHelper");
|
||||||
|
|
||||||
if ((requestCode & mId) != mId) {
|
if ((requestCode & HELPER_ID_BITMASK) != mHelperId) {
|
||||||
// this wasn't meant for us to handle
|
// this wasn't meant for us to handle
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Log.d(Constants.TAG, "handling activity result in OperationHelper");
|
Log.d(Constants.TAG, "handling activity result in OperationHelper");
|
||||||
// filter out mId from requestCode
|
// filter out mHelperId from requestCode
|
||||||
requestCode ^= mId;
|
requestCode ^= mHelperId;
|
||||||
|
|
||||||
if (resultCode == Activity.RESULT_CANCELED) {
|
if (resultCode == Activity.RESULT_CANCELED) {
|
||||||
mCallback.onCryptoOperationCancelled();
|
mCallback.onCryptoOperationCancelled();
|
||||||
@@ -313,7 +317,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void cryptoOperation() {
|
public void cryptoOperation() {
|
||||||
cryptoOperation(new CryptoInputParcel());
|
cryptoOperation(new CryptoInputParcel(new Date()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onHandleResult(OperationResult result) {
|
public void onHandleResult(OperationResult result) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
@@ -119,14 +120,19 @@ public class FileDialogFragment extends DialogFragment {
|
|||||||
mFilename = (EditText) view.findViewById(R.id.input);
|
mFilename = (EditText) view.findViewById(R.id.input);
|
||||||
mFilename.setText(mFile.getName());
|
mFilename.setText(mFile.getName());
|
||||||
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
||||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
public void onClick(View v) {
|
mBrowse.setVisibility(View.GONE);
|
||||||
// only .asc or .gpg files
|
} else {
|
||||||
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||||
// or gpg types!
|
public void onClick(View v) {
|
||||||
FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE);
|
// only .asc or .gpg files
|
||||||
}
|
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
||||||
});
|
// or gpg types!
|
||||||
|
FileHelper.saveDocumentKitKat(
|
||||||
|
FileDialogFragment.this, "*/*", mFile.getName(), REQUEST_CODE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
|
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
|
||||||
if (checkboxText == null) {
|
if (checkboxText == null) {
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package org.sufficientlysecure.keychain.ui.linked;
|
||||||
|
|
||||||
|
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment {
|
||||||
|
|
||||||
|
protected LinkedIdWizard mLinkedIdWizard;
|
||||||
|
|
||||||
|
private ImageView mVerifyImage;
|
||||||
|
private TextView mVerifyStatus;
|
||||||
|
private ViewAnimator mVerifyAnimator;
|
||||||
|
|
||||||
|
// This is a resource, set AFTER it has been verified
|
||||||
|
LinkedTokenResource mVerifiedResource = null;
|
||||||
|
private ViewAnimator mVerifyButtonAnimator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
final View view = newView(inflater, container, savedInstanceState);
|
||||||
|
|
||||||
|
View nextButton = view.findViewById(R.id.next_button);
|
||||||
|
if (nextButton != null) {
|
||||||
|
nextButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
cryptoOperation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mVerifyAnimator = (ViewAnimator) view.findViewById(R.id.verify_progress);
|
||||||
|
mVerifyImage = (ImageView) view.findViewById(R.id.verify_image);
|
||||||
|
mVerifyStatus = (TextView) view.findViewById(R.id.verify_status);
|
||||||
|
mVerifyButtonAnimator = (ViewAnimator) view.findViewById(R.id.verify_buttons);
|
||||||
|
|
||||||
|
view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
proofVerify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.button_retry).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
proofVerify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setVerifyProgress(false, null);
|
||||||
|
mVerifyStatus.setText(R.string.linked_verify_pending);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract LinkedTokenResource getResource(OperationLog log);
|
||||||
|
|
||||||
|
private void setVerifyProgress(boolean on, Boolean success) {
|
||||||
|
if (success == null) {
|
||||||
|
mVerifyStatus.setText(R.string.linked_verifying);
|
||||||
|
displayButton(on ? 2 : 0);
|
||||||
|
} else if (success) {
|
||||||
|
mVerifyStatus.setText(R.string.linked_verify_success);
|
||||||
|
mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24dp);
|
||||||
|
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark),
|
||||||
|
PorterDuff.Mode.SRC_IN);
|
||||||
|
displayButton(2);
|
||||||
|
} else {
|
||||||
|
mVerifyStatus.setText(R.string.linked_verify_error);
|
||||||
|
mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24dp);
|
||||||
|
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark),
|
||||||
|
PorterDuff.Mode.SRC_IN);
|
||||||
|
displayButton(1);
|
||||||
|
}
|
||||||
|
mVerifyAnimator.setDisplayedChild(on ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayButton(int button) {
|
||||||
|
if (mVerifyButtonAnimator.getDisplayedChild() == button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mVerifyButtonAnimator.setDisplayedChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void proofVerify() {
|
||||||
|
setVerifyProgress(true, null);
|
||||||
|
|
||||||
|
new AsyncTask<Void,Void,LinkedVerifyResult>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LinkedVerifyResult doInBackground(Void... params) {
|
||||||
|
long timer = System.currentTimeMillis();
|
||||||
|
|
||||||
|
OperationLog log = new OperationLog();
|
||||||
|
LinkedTokenResource resource = getResource(log);
|
||||||
|
if (resource == null) {
|
||||||
|
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedVerifyResult result = resource.verify(getActivity(), mLinkedIdWizard.mFingerprint);
|
||||||
|
|
||||||
|
// ux flow: this operation should take at last a second
|
||||||
|
timer = System.currentTimeMillis() -timer;
|
||||||
|
if (timer < 1000) try {
|
||||||
|
Thread.sleep(1000 -timer);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// never mind
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success()) {
|
||||||
|
mVerifiedResource = resource;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(LinkedVerifyResult result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
if (result.success()) {
|
||||||
|
setVerifyProgress(false, true);
|
||||||
|
} else {
|
||||||
|
setVerifyProgress(false, false);
|
||||||
|
// on error, show error message
|
||||||
|
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cryptoOperation() {
|
||||||
|
if (mVerifiedResource == null) {
|
||||||
|
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||||
|
.show(LinkedIdCreateFinalFragment.this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.cryptoOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||||
|
if (mVerifiedResource == null) {
|
||||||
|
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||||
|
.show(LinkedIdCreateFinalFragment.this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.cryptoOperation(cryptoInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Parcelable createOperationInput() {
|
||||||
|
SaveKeyringParcel skp =
|
||||||
|
new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
|
||||||
|
|
||||||
|
WrappedUserAttribute ua =
|
||||||
|
LinkedAttribute.fromResource(mVerifiedResource).toUserAttribute();
|
||||||
|
|
||||||
|
skp.mAddUserAttribute.add(ua);
|
||||||
|
|
||||||
|
return skp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationSuccess(OperationResult result) {
|
||||||
|
// if bad -> display here!
|
||||||
|
if (!result.success()) {
|
||||||
|
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationError(OperationResult result) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,706 @@
|
|||||||
|
/*
|
||||||
|
* 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.linked;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnDismissListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.webkit.CookieManager;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
import org.sufficientlysecure.keychain.BuildConfig;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
public class LinkedIdCreateGithubFragment extends CryptoOperationFragment<SaveKeyringParcel,EditKeyResult> {
|
||||||
|
|
||||||
|
public static final String ARG_GITHUB_COOKIE = "github_cookie";
|
||||||
|
private Button mRetryButton;
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
IDLE, AUTH_PROCESS, AUTH_ERROR, POST_PROCESS, POST_ERROR, LID_PROCESS, LID_ERROR, DONE
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewAnimator mButtonContainer;
|
||||||
|
|
||||||
|
StatusIndicator mStatus1, mStatus2, mStatus3;
|
||||||
|
|
||||||
|
byte[] mFingerprint;
|
||||||
|
long mMasterKeyId;
|
||||||
|
private SaveKeyringParcel mSaveKeyringParcel;
|
||||||
|
private TextView mLinkedIdTitle, mLinkedIdComment;
|
||||||
|
private boolean mFinishOnStop;
|
||||||
|
|
||||||
|
public static LinkedIdCreateGithubFragment newInstance() {
|
||||||
|
return new LinkedIdCreateGithubFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedIdCreateGithubFragment() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.linked_create_github_fragment, container, false);
|
||||||
|
|
||||||
|
mButtonContainer = (ViewAnimator) view.findViewById(R.id.button_container);
|
||||||
|
|
||||||
|
mStatus1 = (StatusIndicator) view.findViewById(R.id.linked_status_step1);
|
||||||
|
mStatus2 = (StatusIndicator) view.findViewById(R.id.linked_status_step2);
|
||||||
|
mStatus3 = (StatusIndicator) view.findViewById(R.id.linked_status_step3);
|
||||||
|
|
||||||
|
mRetryButton = (Button) view.findViewById(R.id.button_retry);
|
||||||
|
|
||||||
|
((ImageView) view.findViewById(R.id.linked_id_type_icon)).setImageResource(R.drawable.linked_github);
|
||||||
|
((ImageView) view.findViewById(R.id.linked_id_certified_icon)).setImageResource(R.drawable.octo_link_24dp);
|
||||||
|
mLinkedIdTitle = (TextView) view.findViewById(R.id.linked_id_title);
|
||||||
|
mLinkedIdComment = (TextView) view.findViewById(R.id.linked_id_comment);
|
||||||
|
|
||||||
|
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LinkedIdWizard activity = (LinkedIdWizard) getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activity.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
step1GetOAuthCode();
|
||||||
|
// for animation testing
|
||||||
|
// onCryptoOperationSuccess(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
LinkedIdWizard wizard = (LinkedIdWizard) getActivity();
|
||||||
|
mFingerprint = wizard.mFingerprint;
|
||||||
|
mMasterKeyId = wizard.mMasterKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void step1GetOAuthCode() {
|
||||||
|
|
||||||
|
setState(State.AUTH_PROCESS);
|
||||||
|
|
||||||
|
mButtonContainer.setDisplayedChild(1);
|
||||||
|
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
oAuthRequest("github.com/login/oauth/authorize", BuildConfig.GITHUB_CLIENT_ID, "gist");
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRetryForOAuth() {
|
||||||
|
|
||||||
|
mRetryButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
v.setOnClickListener(null);
|
||||||
|
step1GetOAuthCode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mButtonContainer.setDisplayedChild(3);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void step1GetOAuthToken() {
|
||||||
|
|
||||||
|
if (mOAuthCode == null) {
|
||||||
|
setState(State.AUTH_ERROR);
|
||||||
|
showRetryForOAuth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String gistText = GithubResource.generate(activity, mFingerprint);
|
||||||
|
|
||||||
|
new AsyncTask<Void,Void,JSONObject>() {
|
||||||
|
|
||||||
|
Exception mException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JSONObject doInBackground(Void... dummy) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
JSONObject params = new JSONObject();
|
||||||
|
params.put("client_id", BuildConfig.GITHUB_CLIENT_ID);
|
||||||
|
params.put("client_secret", BuildConfig.GITHUB_CLIENT_SECRET);
|
||||||
|
params.put("code", mOAuthCode);
|
||||||
|
params.put("state", mOAuthState);
|
||||||
|
|
||||||
|
return jsonHttpRequest("https://github.com/login/oauth/access_token", params, null);
|
||||||
|
|
||||||
|
} catch (IOException | HttpResultException e) {
|
||||||
|
mException = e;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new AssertionError("json error, this is a bug!");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(JSONObject result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
// we couldn't show an error anyways
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "response: " + result);
|
||||||
|
|
||||||
|
if (result == null || result.optString("access_token", null) == null) {
|
||||||
|
setState(State.AUTH_ERROR);
|
||||||
|
showRetryForOAuth();
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
Notify.create(activity, R.string.linked_error_auth_failed, Style.ERROR).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mException instanceof SocketTimeoutException) {
|
||||||
|
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
|
||||||
|
} else if (mException instanceof HttpResultException) {
|
||||||
|
Notify.create(activity, activity.getString(R.string.linked_error_http,
|
||||||
|
((HttpResultException) mException).mResponse),
|
||||||
|
Style.ERROR).show();
|
||||||
|
} else if (mException instanceof IOException) {
|
||||||
|
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
step2PostGist(result.optString("access_token"), gistText);
|
||||||
|
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void step2PostGist(final String accessToken, final String gistText) {
|
||||||
|
|
||||||
|
setState(State.POST_PROCESS);
|
||||||
|
|
||||||
|
new AsyncTask<Void,Void,JSONObject>() {
|
||||||
|
|
||||||
|
Exception mException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JSONObject doInBackground(Void... dummy) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
long timer = System.currentTimeMillis();
|
||||||
|
|
||||||
|
JSONObject file = new JSONObject();
|
||||||
|
file.put("content", gistText);
|
||||||
|
|
||||||
|
JSONObject files = new JSONObject();
|
||||||
|
files.put("openpgp.txt", file);
|
||||||
|
|
||||||
|
JSONObject params = new JSONObject();
|
||||||
|
params.put("public", true);
|
||||||
|
params.put("description", getString(R.string.linked_gist_description));
|
||||||
|
params.put("files", files);
|
||||||
|
|
||||||
|
JSONObject result = jsonHttpRequest("https://api.github.com/gists", params, accessToken);
|
||||||
|
|
||||||
|
// ux flow: this operation should take at last a second
|
||||||
|
timer = System.currentTimeMillis() -timer;
|
||||||
|
if (timer < 1000) try {
|
||||||
|
Thread.sleep(1000 -timer);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// never mind
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (IOException | HttpResultException e) {
|
||||||
|
mException = e;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new AssertionError("json error, this is a bug!");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(JSONObject result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "response: " + result);
|
||||||
|
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
// we couldn't show an error anyways
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
setState(State.POST_ERROR);
|
||||||
|
showRetryForOAuth();
|
||||||
|
|
||||||
|
if (mException instanceof SocketTimeoutException) {
|
||||||
|
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
|
||||||
|
} else if (mException instanceof HttpResultException) {
|
||||||
|
Notify.create(activity, activity.getString(R.string.linked_error_http,
|
||||||
|
((HttpResultException) mException).mResponse),
|
||||||
|
Style.ERROR).show();
|
||||||
|
} else if (mException instanceof IOException) {
|
||||||
|
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GithubResource resource;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String gistId = result.getString("id");
|
||||||
|
JSONObject owner = result.getJSONObject("owner");
|
||||||
|
String gistLogin = owner.getString("login");
|
||||||
|
|
||||||
|
URI uri = URI.create("https://gist.github.com/" + gistLogin + "/" + gistId);
|
||||||
|
resource = GithubResource.create(uri);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
setState(State.POST_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
View linkedItem = mButtonContainer.getChildAt(2);
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||||
|
linkedItem.setTransitionName(resource.toUri().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only need authorization for this one operation, drop it afterwards
|
||||||
|
revokeToken(accessToken);
|
||||||
|
|
||||||
|
step3EditKey(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revokeToken(final String token) {
|
||||||
|
|
||||||
|
new AsyncTask<Void,Void,Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... dummy) {
|
||||||
|
try {
|
||||||
|
HttpsURLConnection nection = (HttpsURLConnection) new URL(
|
||||||
|
"https://api.github.com/applications/" + BuildConfig.GITHUB_CLIENT_ID + "/tokens/" + token)
|
||||||
|
.openConnection();
|
||||||
|
nection.setRequestMethod("DELETE");
|
||||||
|
String encoded = Base64.encodeToString(
|
||||||
|
(BuildConfig.GITHUB_CLIENT_ID + ":" + BuildConfig.GITHUB_CLIENT_SECRET).getBytes(), Base64.DEFAULT);
|
||||||
|
nection.setRequestProperty("Authorization", "Basic " + encoded);
|
||||||
|
nection.connect();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// nvm
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void step3EditKey(final GithubResource resource) {
|
||||||
|
|
||||||
|
// set item data while we're there
|
||||||
|
{
|
||||||
|
Context context = getActivity();
|
||||||
|
mLinkedIdTitle.setText(resource.getDisplayTitle(context));
|
||||||
|
mLinkedIdComment.setText(resource.getDisplayComment(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(State.LID_PROCESS);
|
||||||
|
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
WrappedUserAttribute ua = LinkedAttribute.fromResource(resource).toUserAttribute();
|
||||||
|
mSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint);
|
||||||
|
mSaveKeyringParcel.mAddUserAttribute.add(ua);
|
||||||
|
|
||||||
|
cryptoOperation();
|
||||||
|
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public SaveKeyringParcel createOperationInput() {
|
||||||
|
// if this is null, the cryptoOperation silently aborts - which is what we want in that case
|
||||||
|
return mSaveKeyringParcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationSuccess(EditKeyResult result) {
|
||||||
|
|
||||||
|
setState(State.DONE);
|
||||||
|
|
||||||
|
mButtonContainer.getInAnimation().setDuration(750);
|
||||||
|
mButtonContainer.setDisplayedChild(2);
|
||||||
|
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FragmentActivity activity = getActivity();
|
||||||
|
Intent intent = new Intent(activity, ViewKeyActivity.class);
|
||||||
|
intent.setData(KeyRings.buildGenericKeyRingUri(mMasterKeyId));
|
||||||
|
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
intent.putExtra(ViewKeyActivity.EXTRA_LINKED_TRANSITION, true);
|
||||||
|
View linkedItem = mButtonContainer.getChildAt(2);
|
||||||
|
|
||||||
|
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
|
activity, linkedItem, linkedItem.getTransitionName()).toBundle();
|
||||||
|
activity.startActivity(intent, options);
|
||||||
|
mFinishOnStop = true;
|
||||||
|
} else {
|
||||||
|
activity.startActivity(intent);
|
||||||
|
activity.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
// cookies are automatically saved, we don't want that
|
||||||
|
CookieManager cookieManager = CookieManager.getInstance();
|
||||||
|
String cookie = cookieManager.getCookie("https://github.com/");
|
||||||
|
outState.putString(ARG_GITHUB_COOKIE, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
String cookie = savedInstanceState.getString(ARG_GITHUB_COOKIE);
|
||||||
|
CookieManager cookieManager = CookieManager.getInstance();
|
||||||
|
cookieManager.setCookie("https://github.com/", cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
try {
|
||||||
|
// cookies are automatically saved, we don't want that
|
||||||
|
CookieManager cookieManager = CookieManager.getInstance();
|
||||||
|
// noinspection deprecation (replacement is api lvl 21)
|
||||||
|
cookieManager.removeAllCookie();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// no biggie if this fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
if (mFinishOnStop) {
|
||||||
|
Activity activity = getActivity();
|
||||||
|
activity.setResult(Activity.RESULT_OK);
|
||||||
|
activity.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationError(EditKeyResult result) {
|
||||||
|
result.createNotify(getActivity()).show(this);
|
||||||
|
setState(State.LID_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationCancelled() {
|
||||||
|
mRetryButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
v.setOnClickListener(null);
|
||||||
|
mButtonContainer.setDisplayedChild(1);
|
||||||
|
setState(State.LID_PROCESS);
|
||||||
|
cryptoOperation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mButtonContainer.setDisplayedChild(3);
|
||||||
|
setState(State.LID_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String mOAuthCode, mOAuthState;
|
||||||
|
|
||||||
|
public void oAuthRequest(String hostAndPath, String clientId, String scope) {
|
||||||
|
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buf = new byte[16];
|
||||||
|
new Random().nextBytes(buf);
|
||||||
|
mOAuthState = new String(Hex.encode(buf));
|
||||||
|
mOAuthCode = null;
|
||||||
|
|
||||||
|
final Dialog auth_dialog = new Dialog(activity);
|
||||||
|
auth_dialog.setContentView(R.layout.oauth_webview);
|
||||||
|
WebView web = (WebView) auth_dialog.findViewById(R.id.web_view);
|
||||||
|
web.getSettings().setSaveFormData(false);
|
||||||
|
web.getSettings().setUserAgentString("OpenKeychain " + BuildConfig.VERSION_NAME);
|
||||||
|
web.setWebViewClient(new WebViewClient() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
if ("oauth-openkeychain".equals(uri.getScheme())) {
|
||||||
|
|
||||||
|
if (mOAuthCode != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.getQueryParameter("error") != null) {
|
||||||
|
Log.i(Constants.TAG, "got oauth error: " + uri.getQueryParameter("error"));
|
||||||
|
auth_dialog.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if mOAuthState == queryParam[state]
|
||||||
|
mOAuthCode = uri.getQueryParameter("code");
|
||||||
|
|
||||||
|
auth_dialog.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// don't surf away from github!
|
||||||
|
if (!"github.com".equals(uri.getHost())) {
|
||||||
|
auth_dialog.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
auth_dialog.setTitle(R.string.linked_webview_title_github);
|
||||||
|
auth_dialog.setCancelable(true);
|
||||||
|
auth_dialog.setOnDismissListener(new OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
step1GetOAuthToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
auth_dialog.show();
|
||||||
|
|
||||||
|
web.loadUrl("https://" + hostAndPath +
|
||||||
|
"?client_id=" + clientId +
|
||||||
|
"&scope=" + scope +
|
||||||
|
"&redirect_uri=oauth-openkeychain://linked/" +
|
||||||
|
"&state=" + mOAuthState);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(State state) {
|
||||||
|
switch (state) {
|
||||||
|
case IDLE:
|
||||||
|
mStatus1.setDisplayedChild(Status.IDLE);
|
||||||
|
mStatus2.setDisplayedChild(Status.IDLE);
|
||||||
|
mStatus3.setDisplayedChild(Status.IDLE);
|
||||||
|
break;
|
||||||
|
case AUTH_PROCESS:
|
||||||
|
mStatus1.setDisplayedChild(Status.PROGRESS);
|
||||||
|
mStatus2.setDisplayedChild(Status.IDLE);
|
||||||
|
mStatus3.setDisplayedChild(Status.IDLE);
|
||||||
|
break;
|
||||||
|
case AUTH_ERROR:
|
||||||
|
mStatus1.setDisplayedChild(Status.ERROR);
|
||||||
|
mStatus2.setDisplayedChild(Status.IDLE);
|
||||||
|
mStatus3.setDisplayedChild(Status.IDLE);
|
||||||
|
break;
|
||||||
|
case POST_PROCESS:
|
||||||
|
mStatus1.setDisplayedChild(Status.OK);
|
||||||
|
mStatus2.setDisplayedChild(Status.PROGRESS);
|
||||||
|
mStatus3.setDisplayedChild(Status.IDLE);
|
||||||
|
break;
|
||||||
|
case POST_ERROR:
|
||||||
|
mStatus1.setDisplayedChild(Status.OK);
|
||||||
|
mStatus2.setDisplayedChild(Status.ERROR);
|
||||||
|
mStatus3.setDisplayedChild(Status.IDLE);
|
||||||
|
break;
|
||||||
|
case LID_PROCESS:
|
||||||
|
mStatus1.setDisplayedChild(Status.OK);
|
||||||
|
mStatus2.setDisplayedChild(Status.OK);
|
||||||
|
mStatus3.setDisplayedChild(Status.PROGRESS);
|
||||||
|
break;
|
||||||
|
case LID_ERROR:
|
||||||
|
mStatus1.setDisplayedChild(Status.OK);
|
||||||
|
mStatus2.setDisplayedChild(Status.OK);
|
||||||
|
mStatus3.setDisplayedChild(Status.ERROR);
|
||||||
|
break;
|
||||||
|
case DONE:
|
||||||
|
mStatus1.setDisplayedChild(Status.OK);
|
||||||
|
mStatus2.setDisplayedChild(Status.OK);
|
||||||
|
mStatus3.setDisplayedChild(Status.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JSONObject jsonHttpRequest(String url, JSONObject params, String accessToken)
|
||||||
|
throws IOException, HttpResultException {
|
||||||
|
|
||||||
|
HttpsURLConnection nection = (HttpsURLConnection) new URL(url).openConnection();
|
||||||
|
nection.setDoInput(true);
|
||||||
|
nection.setDoOutput(true);
|
||||||
|
nection.setConnectTimeout(2000);
|
||||||
|
nection.setReadTimeout(1000);
|
||||||
|
nection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
nection.setRequestProperty("Accept", "application/json");
|
||||||
|
nection.setRequestProperty("User-Agent", "OpenKeychain " + BuildConfig.VERSION_NAME);
|
||||||
|
if (accessToken != null) {
|
||||||
|
nection.setRequestProperty("Authorization", "token " + accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream os = nection.getOutputStream();
|
||||||
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
|
||||||
|
writer.write(params.toString());
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
os.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
nection.connect();
|
||||||
|
|
||||||
|
int code = nection.getResponseCode();
|
||||||
|
if (code != HttpsURLConnection.HTTP_CREATED && code != HttpsURLConnection.HTTP_OK) {
|
||||||
|
throw new HttpResultException(nection.getResponseCode(), nection.getResponseMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream in = new BufferedInputStream(nection.getInputStream());
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||||
|
StringBuilder response = new StringBuilder();
|
||||||
|
while (true) {
|
||||||
|
String line = reader.readLine();
|
||||||
|
if (line == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
response.append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new JSONObject(response.toString());
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
nection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class HttpResultException extends Exception {
|
||||||
|
final int mCode;
|
||||||
|
final String mResponse;
|
||||||
|
|
||||||
|
HttpResultException(int code, String response) {
|
||||||
|
mCode = code;
|
||||||
|
mResponse = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* 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.linked;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Patterns;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||||
|
|
||||||
|
public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
|
||||||
|
|
||||||
|
LinkedIdWizard mLinkedIdWizard;
|
||||||
|
|
||||||
|
EditText mEditUri;
|
||||||
|
|
||||||
|
public static LinkedIdCreateHttpsStep1Fragment newInstance() {
|
||||||
|
LinkedIdCreateHttpsStep1Fragment frag = new LinkedIdCreateHttpsStep1Fragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
final View view = inflater.inflate(R.layout.linked_create_https_fragment_step1, container, false);
|
||||||
|
|
||||||
|
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
String uri = "https://" + mEditUri.getText();
|
||||||
|
|
||||||
|
if (!checkUri(uri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String proofText = GenericHttpsResource.generateText(getActivity(),
|
||||||
|
mLinkedIdWizard.mFingerprint);
|
||||||
|
|
||||||
|
LinkedIdCreateHttpsStep2Fragment frag =
|
||||||
|
LinkedIdCreateHttpsStep2Fragment.newInstance(uri, proofText);
|
||||||
|
|
||||||
|
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri);
|
||||||
|
|
||||||
|
mEditUri.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable editable) {
|
||||||
|
String uri = "https://" + editable;
|
||||||
|
if (uri.length() > 0) {
|
||||||
|
if (checkUri(uri)) {
|
||||||
|
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||||
|
R.drawable.ic_stat_retyped_ok, 0);
|
||||||
|
} else {
|
||||||
|
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||||
|
R.drawable.ic_stat_retyped_bad, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// remove drawable if email is empty
|
||||||
|
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// mEditUri.setText("mugenguild.com/pgpkey.txt");
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkUri(String uri) {
|
||||||
|
return Patterns.WEB_URL.matcher(uri).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* 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.linked;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment {
|
||||||
|
|
||||||
|
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
|
||||||
|
|
||||||
|
public static final String ARG_URI = "uri", ARG_TEXT = "text";
|
||||||
|
|
||||||
|
EditText mEditUri;
|
||||||
|
|
||||||
|
URI mResourceUri;
|
||||||
|
String mResourceString;
|
||||||
|
|
||||||
|
public static LinkedIdCreateHttpsStep2Fragment newInstance
|
||||||
|
(String uri, String proofText) {
|
||||||
|
|
||||||
|
LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(ARG_URI, uri);
|
||||||
|
args.putString(ARG_TEXT, proofText);
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
GenericHttpsResource getResource(OperationLog log) {
|
||||||
|
return GenericHttpsResource.createNew(mResourceUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mResourceUri = new URI(getArguments().getString(ARG_URI));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
mResourceString = getArguments().getString(ARG_TEXT);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected View newView(LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
|
||||||
|
if (view != null) {
|
||||||
|
|
||||||
|
view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
proofSend();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
proofSave();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri);
|
||||||
|
mEditUri.setText(mResourceUri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void proofSend () {
|
||||||
|
Intent sendIntent = new Intent();
|
||||||
|
sendIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString);
|
||||||
|
sendIntent.setType("text/plain");
|
||||||
|
startActivity(sendIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void proofSave () {
|
||||||
|
String state = Environment.getExternalStorageState();
|
||||||
|
if (!Environment.MEDIA_MOUNTED.equals(state)) {
|
||||||
|
Notify.create(getActivity(), "External storage not available!", Style.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetName = "pgpkey.txt";
|
||||||
|
|
||||||
|
FileHelper.saveDocument(this,
|
||||||
|
targetName, Uri.fromFile(new File(Constants.Path.APP_DIR, targetName)),
|
||||||
|
"text/plain", R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to,
|
||||||
|
REQUEST_CODE_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveFile(Uri uri) {
|
||||||
|
try {
|
||||||
|
PrintWriter out =
|
||||||
|
new PrintWriter(getActivity().getContentResolver().openOutputStream(uri));
|
||||||
|
out.print(mResourceString);
|
||||||
|
if (out.checkError()) {
|
||||||
|
Notify.create(getActivity(), "Error writing file!", Style.ERROR).show();
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
Notify.create(getActivity(), "File could not be opened for writing!", Style.ERROR).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
switch (requestCode) {
|
||||||
|
// For saving a file
|
||||||
|
case REQUEST_CODE_OUTPUT:
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uri uri = data.getData();
|
||||||
|
saveFile(uri);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* 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.linked;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.EditText;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
|
||||||
|
public class LinkedIdCreateTwitterStep1Fragment extends Fragment {
|
||||||
|
|
||||||
|
LinkedIdWizard mLinkedIdWizard;
|
||||||
|
|
||||||
|
EditText mEditHandle;
|
||||||
|
|
||||||
|
public static LinkedIdCreateTwitterStep1Fragment newInstance() {
|
||||||
|
LinkedIdCreateTwitterStep1Fragment frag = new LinkedIdCreateTwitterStep1Fragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false);
|
||||||
|
|
||||||
|
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
final String handle = mEditHandle.getText().toString();
|
||||||
|
|
||||||
|
if ("".equals(handle)) {
|
||||||
|
mEditHandle.setError("Please input a Twitter handle!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new AsyncTask<Void,Void,Boolean>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
return true;
|
||||||
|
// return checkHandle(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
Notify.create(getActivity(),
|
||||||
|
"Connection error while checking username!",
|
||||||
|
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
Notify.create(getActivity(),
|
||||||
|
"This handle does not exist on Twitter!",
|
||||||
|
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedIdCreateTwitterStep2Fragment frag =
|
||||||
|
LinkedIdCreateTwitterStep2Fragment.newInstance(handle);
|
||||||
|
|
||||||
|
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mEditHandle = (EditText) view.findViewById(R.id.linked_create_twitter_handle);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* not used at this point, too many problems
|
||||||
|
private static Boolean checkHandle(String handle) {
|
||||||
|
try {
|
||||||
|
HttpURLConnection nection =
|
||||||
|
(HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection();
|
||||||
|
nection.setRequestMethod("HEAD");
|
||||||
|
nection.setRequestProperty("User-Agent", "OpenKeychain");
|
||||||
|
return nection.getResponseCode() == 200;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.linked;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
|
||||||
|
|
||||||
|
public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragment {
|
||||||
|
|
||||||
|
public static final String ARG_HANDLE = "handle";
|
||||||
|
|
||||||
|
String mResourceHandle;
|
||||||
|
String mResourceString;
|
||||||
|
|
||||||
|
public static LinkedIdCreateTwitterStep2Fragment newInstance
|
||||||
|
(String handle) {
|
||||||
|
|
||||||
|
LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(ARG_HANDLE, handle);
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mResourceString =
|
||||||
|
TwitterResource.generate(mLinkedIdWizard.mFingerprint);
|
||||||
|
|
||||||
|
mResourceHandle = getArguments().getString(ARG_HANDLE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
|
||||||
|
if (view != null) {
|
||||||
|
view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
proofSend();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.button_share).setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
proofShare();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
((TextView) view.findViewById(R.id.linked_tweet_published)).setText(
|
||||||
|
Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
LinkedTokenResource getResource(OperationLog log) {
|
||||||
|
return TwitterResource.searchInTwitterStream(getActivity(),
|
||||||
|
mResourceHandle, mResourceString, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void proofShare() {
|
||||||
|
Intent sendIntent = new Intent();
|
||||||
|
sendIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString);
|
||||||
|
sendIntent.setType("text/plain");
|
||||||
|
startActivity(sendIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void proofSend() {
|
||||||
|
|
||||||
|
Uri.Builder builder = Uri.parse("https://twitter.com/intent/tweet").buildUpon();
|
||||||
|
builder.appendQueryParameter("text", mResourceString);
|
||||||
|
Uri uri = builder.build();
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
getActivity().startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* 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.linked;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class LinkedIdSelectFragment extends Fragment {
|
||||||
|
|
||||||
|
LinkedIdWizard mLinkedIdWizard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new instance of this fragment
|
||||||
|
*/
|
||||||
|
public static LinkedIdSelectFragment newInstance() {
|
||||||
|
LinkedIdSelectFragment frag = new LinkedIdSelectFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.linked_select_fragment, container, false);
|
||||||
|
|
||||||
|
view.findViewById(R.id.linked_create_https_button)
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LinkedIdCreateHttpsStep1Fragment frag =
|
||||||
|
LinkedIdCreateHttpsStep1Fragment.newInstance();
|
||||||
|
|
||||||
|
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
view.findViewById(R.id.linked_create_dns_button)
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LinkedIdCreateDnsStep1Fragment frag =
|
||||||
|
LinkedIdCreateDnsStep1Fragment.newInstance();
|
||||||
|
|
||||||
|
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
view.findViewById(R.id.linked_create_twitter_button)
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LinkedIdCreateTwitterStep1Fragment frag =
|
||||||
|
LinkedIdCreateTwitterStep1Fragment.newInstance();
|
||||||
|
|
||||||
|
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view.findViewById(R.id.linked_create_github_button)
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LinkedIdCreateGithubFragment frag =
|
||||||
|
LinkedIdCreateGithubFragment.newInstance();
|
||||||
|
|
||||||
|
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,560 @@
|
|||||||
|
package org.sufficientlysecure.keychain.ui.linked;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
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.ImageView;
|
||||||
|
import android.widget.TextSwitcher;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.Constants.key;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.linked.LinkedResource;
|
||||||
|
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||||
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||||
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||||
|
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.ViewHolder.VerifyState;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
public class LinkedIdViewFragment extends CryptoOperationFragment implements
|
||||||
|
LoaderManager.LoaderCallbacks<Cursor>, OnBackStackChangedListener {
|
||||||
|
|
||||||
|
private static final String ARG_DATA_URI = "data_uri";
|
||||||
|
private static final String ARG_LID_RANK = "rank";
|
||||||
|
private static final String ARG_IS_SECRET = "verified";
|
||||||
|
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||||
|
private static final int LOADER_ID_LINKED_ID = 1;
|
||||||
|
|
||||||
|
private UriAttribute mLinkedId;
|
||||||
|
private LinkedTokenResource mLinkedResource;
|
||||||
|
private boolean mIsSecret;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private byte[] mFingerprint;
|
||||||
|
|
||||||
|
private AsyncTask mInProgress;
|
||||||
|
|
||||||
|
private Uri mDataUri;
|
||||||
|
private ViewHolder mViewHolder;
|
||||||
|
private int mLidRank;
|
||||||
|
private OnIdentityLoadedListener mIdLoadedListener;
|
||||||
|
private long mCertifyKeyId;
|
||||||
|
|
||||||
|
public static LinkedIdViewFragment newInstance(Uri dataUri, int rank,
|
||||||
|
boolean isSecret, byte[] fingerprint) throws IOException {
|
||||||
|
LinkedIdViewFragment frag = new LinkedIdViewFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||||
|
args.putInt(ARG_LID_RANK, rank);
|
||||||
|
args.putBoolean(ARG_IS_SECRET, isSecret);
|
||||||
|
args.putByteArray(ARG_FINGERPRINT, fingerprint);
|
||||||
|
frag.setArguments(args);
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedIdViewFragment() {
|
||||||
|
// IMPORTANT: the id must be unique in the ViewKeyActivity CryptoOperationHelper id namespace!
|
||||||
|
// no initial progress message -> we handle progress ourselves!
|
||||||
|
super(5, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Bundle args = getArguments();
|
||||||
|
mDataUri = args.getParcelable(ARG_DATA_URI);
|
||||||
|
mLidRank = args.getInt(ARG_LID_RANK);
|
||||||
|
|
||||||
|
mIsSecret = args.getBoolean(ARG_IS_SECRET);
|
||||||
|
mFingerprint = args.getByteArray(ARG_FINGERPRINT);
|
||||||
|
|
||||||
|
mContext = getActivity();
|
||||||
|
|
||||||
|
getLoaderManager().initLoader(LOADER_ID_LINKED_ID, null, this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
switch (id) {
|
||||||
|
case LOADER_ID_LINKED_ID:
|
||||||
|
return new CursorLoader(getActivity(), mDataUri,
|
||||||
|
UserIdsAdapter.USER_PACKETS_PROJECTION,
|
||||||
|
Tables.USER_PACKETS + "." + UserPackets.RANK
|
||||||
|
+ " = " + Integer.toString(mLidRank), null, null);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||||
|
switch (loader.getId()) {
|
||||||
|
case LOADER_ID_LINKED_ID:
|
||||||
|
|
||||||
|
// Nothing to load means break if we are *expected* to load
|
||||||
|
if (!cursor.moveToFirst()) {
|
||||||
|
if (mIdLoadedListener != null) {
|
||||||
|
Notify.create(getActivity(), "Error loading identity!",
|
||||||
|
Notify.LENGTH_LONG, Style.ERROR).show();
|
||||||
|
finishFragment();
|
||||||
|
}
|
||||||
|
// Or just ignore, this is probably some intermediate state during certify
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int certStatus = cursor.getInt(UserIdsAdapter.INDEX_VERIFIED);
|
||||||
|
|
||||||
|
byte[] data = cursor.getBlob(UserIdsAdapter.INDEX_ATTRIBUTE_DATA);
|
||||||
|
UriAttribute linkedId = LinkedAttribute.fromAttributeData(data);
|
||||||
|
|
||||||
|
loadIdentity(linkedId, certStatus);
|
||||||
|
|
||||||
|
if (mIdLoadedListener != null) {
|
||||||
|
mIdLoadedListener.onIdentityLoaded();
|
||||||
|
mIdLoadedListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "error parsing identity", e);
|
||||||
|
Notify.create(getActivity(), "Error parsing identity!",
|
||||||
|
Notify.LENGTH_LONG, Style.ERROR).show();
|
||||||
|
finishFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishFragment() {
|
||||||
|
new Handler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
FragmentManager manager = getFragmentManager();
|
||||||
|
manager.removeOnBackStackChangedListener(LinkedIdViewFragment.this);
|
||||||
|
manager.popBackStack("linked_id", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnIdentityLoadedListener {
|
||||||
|
void onIdentityLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnIdentityLoadedListener(OnIdentityLoadedListener listener) {
|
||||||
|
mIdLoadedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadIdentity(UriAttribute linkedId, int certStatus) {
|
||||||
|
mLinkedId = linkedId;
|
||||||
|
|
||||||
|
if (mLinkedId instanceof LinkedAttribute) {
|
||||||
|
LinkedResource res = ((LinkedAttribute) mLinkedId).mResource;
|
||||||
|
mLinkedResource = (LinkedTokenResource) res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsSecret) {
|
||||||
|
switch (certStatus) {
|
||||||
|
case Certs.VERIFIED_SECRET:
|
||||||
|
KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
|
||||||
|
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||||
|
break;
|
||||||
|
case Certs.VERIFIED_SELF:
|
||||||
|
KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
|
||||||
|
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
|
||||||
|
null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mViewHolder.mLinkedIdHolder.vVerified.setImageResource(R.drawable.octo_link_24dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
mViewHolder.mLinkedIdHolder.setData(mContext, mLinkedId);
|
||||||
|
|
||||||
|
setShowVerifying(false);
|
||||||
|
|
||||||
|
// no resource, nothing further we can do…
|
||||||
|
if (mLinkedResource == null) {
|
||||||
|
mViewHolder.vButtonView.setVisibility(View.GONE);
|
||||||
|
mViewHolder.vButtonVerify.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mLinkedResource.isViewable()) {
|
||||||
|
mViewHolder.vButtonView.setVisibility(View.VISIBLE);
|
||||||
|
mViewHolder.vButtonView.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent intent = mLinkedResource.getViewIntent();
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getActivity().startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mViewHolder.vButtonView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder {
|
||||||
|
private final View vButtonView;
|
||||||
|
private final ViewAnimator vVerifyingContainer;
|
||||||
|
private final ViewAnimator vItemCertified;
|
||||||
|
private final View vKeySpinnerContainer;
|
||||||
|
LinkedIdsAdapter.ViewHolder mLinkedIdHolder;
|
||||||
|
|
||||||
|
private ViewAnimator vButtonSwitcher;
|
||||||
|
private CertListWidget vLinkedCerts;
|
||||||
|
private CertifyKeySpinner vKeySpinner;
|
||||||
|
private final View vButtonVerify;
|
||||||
|
private final View vButtonRetry;
|
||||||
|
private final View vButtonConfirm;
|
||||||
|
|
||||||
|
private final ViewAnimator vProgress;
|
||||||
|
private final TextSwitcher vText;
|
||||||
|
|
||||||
|
ViewHolder(View root) {
|
||||||
|
vLinkedCerts = (CertListWidget) root.findViewById(R.id.linked_id_certs);
|
||||||
|
vKeySpinner = (CertifyKeySpinner) root.findViewById(R.id.cert_key_spinner);
|
||||||
|
vKeySpinnerContainer = root.findViewById(R.id.cert_key_spincontainer);
|
||||||
|
vButtonSwitcher = (ViewAnimator) root.findViewById(R.id.button_animator);
|
||||||
|
|
||||||
|
mLinkedIdHolder = new LinkedIdsAdapter.ViewHolder(root);
|
||||||
|
|
||||||
|
vButtonVerify = root.findViewById(R.id.button_verify);
|
||||||
|
vButtonRetry = root.findViewById(R.id.button_retry);
|
||||||
|
vButtonConfirm = root.findViewById(R.id.button_confirm);
|
||||||
|
vButtonView = root.findViewById(R.id.button_view);
|
||||||
|
|
||||||
|
vVerifyingContainer = (ViewAnimator) root.findViewById(R.id.linked_verify_container);
|
||||||
|
vItemCertified = (ViewAnimator) root.findViewById(R.id.linked_id_certified);
|
||||||
|
|
||||||
|
vProgress = (ViewAnimator) root.findViewById(R.id.linked_cert_progress);
|
||||||
|
vText = (TextSwitcher) root.findViewById(R.id.linked_cert_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VerifyState {
|
||||||
|
VERIFYING, VERIFY_OK, VERIFY_ERROR, CERTIFYING
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVerifyingState(Context context, VerifyState state, boolean isSecret) {
|
||||||
|
switch (state) {
|
||||||
|
case VERIFYING:
|
||||||
|
vProgress.setDisplayedChild(0);
|
||||||
|
vText.setText(context.getString(R.string.linked_text_verifying));
|
||||||
|
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VERIFY_OK:
|
||||||
|
vProgress.setDisplayedChild(1);
|
||||||
|
if (!isSecret) {
|
||||||
|
showButton(2);
|
||||||
|
if (!vKeySpinner.isSingleEntry()) {
|
||||||
|
vKeySpinnerContainer.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showButton(1);
|
||||||
|
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VERIFY_ERROR:
|
||||||
|
showButton(1);
|
||||||
|
vProgress.setDisplayedChild(2);
|
||||||
|
vText.setText(context.getString(R.string.linked_text_error));
|
||||||
|
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CERTIFYING:
|
||||||
|
vProgress.setDisplayedChild(0);
|
||||||
|
vText.setText(context.getString(R.string.linked_text_confirming));
|
||||||
|
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showVerifyingContainer(Context context, boolean show, boolean isSecret) {
|
||||||
|
if (vVerifyingContainer.getDisplayedChild() == (show ? 1 : 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vVerifyingContainer.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
|
||||||
|
vVerifyingContainer.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
|
||||||
|
vVerifyingContainer.setDisplayedChild(show ? 1 : 0);
|
||||||
|
|
||||||
|
vItemCertified.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
|
||||||
|
vItemCertified.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
|
||||||
|
vItemCertified.setDisplayedChild(show || isSecret ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showButton(int which) {
|
||||||
|
if (vButtonSwitcher.getDisplayedChild() == which) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vButtonSwitcher.setDisplayedChild(which);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean mVerificationState = false;
|
||||||
|
/** Switches between the 'verifying' ui bit and certificate status. This method
|
||||||
|
* must behave correctly in all states, showing or hiding the appropriate views
|
||||||
|
* and cancelling pending operations where necessary.
|
||||||
|
*
|
||||||
|
* This method also handles back button functionality in combination with
|
||||||
|
* onBackStateChanged.
|
||||||
|
*/
|
||||||
|
void setShowVerifying(boolean show) {
|
||||||
|
if (!show) {
|
||||||
|
if (mInProgress != null) {
|
||||||
|
mInProgress.cancel(false);
|
||||||
|
mInProgress = null;
|
||||||
|
}
|
||||||
|
getFragmentManager().removeOnBackStackChangedListener(this);
|
||||||
|
new Handler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getFragmentManager().popBackStack("verification",
|
||||||
|
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mVerificationState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mVerificationState = false;
|
||||||
|
|
||||||
|
mViewHolder.showButton(0);
|
||||||
|
mViewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
|
||||||
|
mViewHolder.showVerifyingContainer(mContext, false, mIsSecret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mVerificationState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mVerificationState = true;
|
||||||
|
|
||||||
|
FragmentManager manager = getFragmentManager();
|
||||||
|
manager.beginTransaction().addToBackStack("verification").commit();
|
||||||
|
manager.executePendingTransactions();
|
||||||
|
manager.addOnBackStackChangedListener(this);
|
||||||
|
mViewHolder.showVerifyingContainer(mContext, true, mIsSecret);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackStackChanged() {
|
||||||
|
setShowVerifying(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||||
|
View root = inflater.inflate(R.layout.linked_id_view_fragment, null);
|
||||||
|
|
||||||
|
mViewHolder = new ViewHolder(root);
|
||||||
|
root.setTag(mViewHolder);
|
||||||
|
|
||||||
|
((ImageView) root.findViewById(R.id.status_icon_verified))
|
||||||
|
.setColorFilter(mContext.getResources().getColor(R.color.android_green_light),
|
||||||
|
PorterDuff.Mode.SRC_IN);
|
||||||
|
((ImageView) root.findViewById(R.id.status_icon_invalid))
|
||||||
|
.setColorFilter(mContext.getResources().getColor(R.color.android_red_light),
|
||||||
|
PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
|
mViewHolder.vButtonVerify.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
verifyResource();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mViewHolder.vButtonRetry.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
verifyResource();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mViewHolder.vButtonConfirm.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
initiateCertifying();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putParcelable(CertListWidget.ARG_URI, Certs.buildLinkedIdCertsUri(mDataUri, mLidRank));
|
||||||
|
args.putBoolean(CertListWidget.ARG_IS_SECRET, mIsSecret);
|
||||||
|
getLoaderManager().initLoader(CertListWidget.LOADER_ID_LINKED_CERTS,
|
||||||
|
args, mViewHolder.vLinkedCerts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void verifyResource() {
|
||||||
|
|
||||||
|
// only one at a time (no sync needed, mInProgress is only touched in ui thread)
|
||||||
|
if (mInProgress != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowVerifying(true);
|
||||||
|
|
||||||
|
mViewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
|
||||||
|
mViewHolder.setVerifyingState(mContext, VerifyState.VERIFYING, mIsSecret);
|
||||||
|
|
||||||
|
mInProgress = new AsyncTask<Void,Void,LinkedVerifyResult>() {
|
||||||
|
@Override
|
||||||
|
protected LinkedVerifyResult doInBackground(Void... params) {
|
||||||
|
long timer = System.currentTimeMillis();
|
||||||
|
LinkedVerifyResult result = mLinkedResource.verify(getActivity(), mFingerprint);
|
||||||
|
|
||||||
|
// ux flow: this operation should take at last a second
|
||||||
|
timer = System.currentTimeMillis() -timer;
|
||||||
|
if (timer < 1000) try {
|
||||||
|
Thread.sleep(1000 -timer);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// never mind
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(LinkedVerifyResult result) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.success()) {
|
||||||
|
mViewHolder.vText.setText(getString(mLinkedResource.getVerifiedText(mIsSecret)));
|
||||||
|
// hack to preserve bold text
|
||||||
|
((TextView) mViewHolder.vText.getCurrentView()).setText(
|
||||||
|
mLinkedResource.getVerifiedText(mIsSecret));
|
||||||
|
mViewHolder.setVerifyingState(mContext, VerifyState.VERIFY_OK, mIsSecret);
|
||||||
|
mViewHolder.mLinkedIdHolder.seekAttention();
|
||||||
|
} else {
|
||||||
|
mViewHolder.setVerifyingState(mContext, VerifyState.VERIFY_ERROR, mIsSecret);
|
||||||
|
result.createNotify(getActivity()).show();
|
||||||
|
}
|
||||||
|
mInProgress = null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initiateCertifying() {
|
||||||
|
|
||||||
|
if (mIsSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the user's passphrase for this key (if required)
|
||||||
|
mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedKeyId();
|
||||||
|
if (mCertifyKeyId == key.none || mCertifyKeyId == key.symmetric) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
SubtleAttentionSeeker.tintBackground(mViewHolder.vKeySpinnerContainer, 600).start();
|
||||||
|
} else {
|
||||||
|
Notify.create(getActivity(), R.string.select_key_to_certify, Style.ERROR).show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mViewHolder.setVerifyingState(mContext, VerifyState.CERTIFYING, false);
|
||||||
|
cryptoOperation();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationCancelled() {
|
||||||
|
super.onCryptoOperationCancelled();
|
||||||
|
|
||||||
|
// go back to 'verified ok'
|
||||||
|
setShowVerifying(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Parcelable createOperationInput() {
|
||||||
|
long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
|
||||||
|
CertifyAction action = new CertifyAction(masterKeyId, null,
|
||||||
|
Collections.singletonList(mLinkedId.toUserAttribute()));
|
||||||
|
|
||||||
|
// fill values for this action
|
||||||
|
CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId);
|
||||||
|
parcel.mCertifyActions.addAll(Collections.singletonList(action));
|
||||||
|
|
||||||
|
return parcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationSuccess(OperationResult result) {
|
||||||
|
result.createNotify(getActivity()).show();
|
||||||
|
// no need to do anything else, we will get a loader refresh!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCryptoOperationError(OperationResult result) {
|
||||||
|
result.createNotify(getActivity()).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||