From a92c04355b84f4ca2a4f376b1ed77611d4218e34 Mon Sep 17 00:00:00 2001 From: mh Date: Mon, 12 Sep 2022 16:54:29 +0200 Subject: [PATCH] fix #6 - enable upload of keys with plain email as uid Our current filtering mechanism did not allow for plain emails as uids. Which is what can be done and some people do it. To safely import keys where there is a plain email in the uid, we can make use of the new mbox import filter, that is available since 2.1.14. With this we can change our filtering (through import & export) approach: 1. we detect if we have a single key through simple `gpg < keymaterial` inspection. 2. if we have a single key, we record the fingerprint 3. We import using the mbox filter with a direct match of the email address 4. We export by matching with the fingerprint This will give us the exact matching key (through fpr) if there is a direct match on the email, which is all delegated to gpg itself. --- .gitignore | 1 + lib/wkd-srv/key.rb | 31 ++++++---- ...F89F7819B2CBD27DC35D627CB498504CAF74FC.pub | 13 +++++ ...5B4E7CCBBAC3CC0E249E683B4327815B714412.pub | 13 +++++ .../.#lk0x0000563e1805d1b0.paperli.30324 | 2 - ...F89F7819B2CBD27DC35D627CB498504CAF74FC.rev | 28 +++++++++ ...5B4E7CCBBAC3CC0E249E683B4327815B714412.rev | 28 +++++++++ ...CB7CC85B537B8E4355DB073112EF9ED78E85AD.key | 5 ++ ...D43376C9F835C6CF705976E764EB1F4F5F6374.key | 5 ++ ...8BFA1C8FE8EEFE856D83BC2DD5B6306DC574CD.key | 5 ++ ...779A201FE3D50D2C651A21438F91EEFC3B86D2.key | 5 ++ spec/gnupg/pubring.kbx | Bin 5360 -> 6592 bytes spec/gnupg/pubring.kbx~ | Bin 5360 -> 5970 bytes spec/gnupg/trustdb.gpg | Bin 1400 -> 1560 bytes spec/wkd-srv/api_spec.rb | 15 ++++- spec/wkd-srv/key_spec.rb | 53 +++++++++++++++++- 16 files changed, 187 insertions(+), 17 deletions(-) create mode 100644 spec/fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.pub create mode 100644 spec/fixtures/keys/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.pub delete mode 100644 spec/gnupg/.#lk0x0000563e1805d1b0.paperli.30324 create mode 100644 spec/gnupg/openpgp-revocs.d/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.rev create mode 100644 spec/gnupg/openpgp-revocs.d/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.rev create mode 100644 spec/gnupg/private-keys-v1.d/3BCB7CC85B537B8E4355DB073112EF9ED78E85AD.key create mode 100644 spec/gnupg/private-keys-v1.d/79D43376C9F835C6CF705976E764EB1F4F5F6374.key create mode 100644 spec/gnupg/private-keys-v1.d/C38BFA1C8FE8EEFE856D83BC2DD5B6306DC574CD.key create mode 100644 spec/gnupg/private-keys-v1.d/FF779A201FE3D50D2C651A21438F91EEFC3B86D2.key diff --git a/.gitignore b/.gitignore index 180bf07..dc664f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .bundle vendor +spec/gnupg/.* diff --git a/lib/wkd-srv/key.rb b/lib/wkd-srv/key.rb index 3da3ea9..63b8087 100644 --- a/lib/wkd-srv/key.rb +++ b/lib/wkd-srv/key.rb @@ -61,23 +61,27 @@ module WkdSrv # We don't check for output or errors because we check for usable keys in the next step. def import(key_material, emailaddr) - gpg(%W[--import-filter keep-uid="uid =~ <#{emailaddr}>" --import]) do |stdin, stdout, stderr| + gpg(%W[--import-filter keep-uid=\"mbox = #{emailaddr}\" --import]) do |stdin, stdout, stderr| stdin.write key_material stdin.close stdout.readlines end end - def export(key_id) - err, output, exitcode = gpg('--export', key_id) + def export(key_fpr) + err, output, exitcode = gpg('--export', key_fpr) raise(err.join("\n")) if exitcode > 0 output.join end def filter_key_material!(key_material, emailaddr) + key_fpr = get_key_fpr!(key_material) import(key_material, emailaddr) - key_id = get_distinct_key_id!(emailaddr) - export(key_id) + result = export(key_fpr) + if result.empty? + raise "Error: key material contains no key matching the given address!" + end + result end def gpg(*args, &block) @@ -118,16 +122,21 @@ module WkdSrv Base32.encode(Digest::SHA1.digest(string.downcase)) end - def get_distinct_key_id!(emailaddr) - _, keylisting, _ = gpg("--with-colons --list-keys '<#{emailaddr}>'") - pub_lines = keylisting.map{|s| sanitize_encoding(s) }.grep(/^pub/) + def get_key_fpr!(key_material) + errors, keylisting, _ = gpg("--with-fingerprint --with-colons") do |stdin, stdout, stderr| + stdin.write key_material + stdin.close + stdout.readlines + end + filtered_output = keylisting.map{|s| sanitize_encoding(s) }.grep(/^(pub|fpr)/) + pub_lines = filtered_output.grep(/^pub/) case pub_lines.size when 0 - raise "Error: key material contains no key matching the given address!" + raise "Error: key material contains no key!" when 1 - pub_lines.first.split(':')[4] + filtered_output.grep(/^fpr/).first.split(':')[9] else - raise "Error: key material contains more than one key matching the given address!" + raise "Error: key material contains more than one key!" end end diff --git a/spec/fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.pub b/spec/fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.pub new file mode 100644 index 0000000..7338064 --- /dev/null +++ b/spec/fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.pub @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEYxeu2hYJKwYBBAHaRw8BAQdAuIIo9IyD3mC84FzG4mSYlUbWsGKosF505IAF +vp+CRMG0Hm90aGVyLXdrZC1zcnYtdGVzdEBleGFtcGxlLm9yZ4iZBBMWCgBBFiEE +i/ifeBmyy9J9w11ifLSYUEyvdPwFAmMXrtoCGwMFCQPCZwAFCwkIBwICIgIGFQoJ +CAsCBBYCAwECHgcCF4AACgkQfLSYUEyvdPzEKQD/Zhq51hukjpnETckRUu3+m272 +mPUqCCvXxi+hPyVzpe8BAM+O9l+1JoX02L94mUTg0oerUYgGzQZXGn7lRKYrUa8G +uDgEYxeu2hIKKwYBBAGXVQEFAQEHQD6/8fm9o4wbyIfyGAnjFPT7VQ5Ul+MNndBr +14qUuEp9AwEIB4h4BBgWCgAgFiEEi/ifeBmyy9J9w11ifLSYUEyvdPwFAmMXrtoC +GwwACgkQfLSYUEyvdPy8HQD+JtKhopUvRnNynXlM9Hw0N9ZhROoaBEoXVvUdBFgb +5HoA/02LRI/nLGSrVA0KrjdeECHy9wOUInvjik21p3J0XBIJ +=GMv+ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/spec/fixtures/keys/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.pub b/spec/fixtures/keys/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.pub new file mode 100644 index 0000000..a93836e --- /dev/null +++ b/spec/fixtures/keys/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.pub @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEYxeyQxYJKwYBBAHaRw8BAQdABs2MGMKJ2wsf3mEfIlIZiCfn3pLVZPJNg/K9 +AYagRqu0KndrZC1zcnYtdGVzdEBleGFtcGxlLm9yZyA8Zm9vQGV4YW1wbGUuY29t +PoiZBBMWCgBBFiEEu1tOfMu6w8wOJJ5oO0MngVtxRBIFAmMXskMCGwMFCQPCZwAF +CwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQO0MngVtxRBKAhAD+LCupoPXl +bmBMkYhbOZ7Zxv4HaFa7XhcfGl5EoghkWwYBAMKmRUaNicfkh3pqPTFbwL0XXxMM +kBcMrEhqbfFmaLQJuDgEYxeyQxIKKwYBBAGXVQEFAQEHQEese4D6ms1KsJfVz32F +OBbl9PaCeq4YR5JTbX5e7PhaAwEIB4h4BBgWCgAgFiEEu1tOfMu6w8wOJJ5oO0Mn +gVtxRBIFAmMXskMCGwwACgkQO0MngVtxRBIrdAEAlUbTaqnvYbx6GqwQypiuRVOY +jte0gjv/FStKwAlSCtMBANYFj9a7oJkEPeikvSyWPZMist6J2wOx0V4wK8DgmzQG +=02vn +-----END PGP PUBLIC KEY BLOCK----- diff --git a/spec/gnupg/.#lk0x0000563e1805d1b0.paperli.30324 b/spec/gnupg/.#lk0x0000563e1805d1b0.paperli.30324 deleted file mode 100644 index 0bbf3be..0000000 --- a/spec/gnupg/.#lk0x0000563e1805d1b0.paperli.30324 +++ /dev/null @@ -1,2 +0,0 @@ - 30324 -paperli diff --git a/spec/gnupg/openpgp-revocs.d/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.rev b/spec/gnupg/openpgp-revocs.d/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.rev new file mode 100644 index 0000000..ee2153e --- /dev/null +++ b/spec/gnupg/openpgp-revocs.d/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.rev @@ -0,0 +1,28 @@ +This is a revocation certificate for the OpenPGP key: + +pub ed25519 2022-09-06 [SC] [expires: 2024-09-05] + 8BF89F7819B2CBD27DC35D627CB498504CAF74FC +uid other-wkd-srv-test@example.org + +A revocation certificate is a kind of "kill switch" to publicly +declare that a key shall not anymore be used. It is not possible +to retract such a revocation certificate once it has been published. + +Use it to revoke this key in case of a compromise or loss of +the secret key. However, if the secret key is still accessible, +it is better to generate a new revocation certificate and give +a reason for the revocation. For details see the description of +of the gpg command "--generate-revocation" in the GnuPG manual. + +To avoid an accidental use of this file, a colon has been inserted +before the 5 dashes below. Remove this colon with a text editor +before importing and publishing this revocation certificate. + +:-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: This is a revocation certificate + +iHgEIBYKACAWIQSL+J94GbLL0n3DXWJ8tJhQTK90/AUCYxevAwIdAAAKCRB8tJhQ +TK90/PYLAQCYEavxd0lPva3Cr+QE11bMu4HJuhOrs5kz2td8H+WOYQEAoldOLL4h +6WVwhvT5nA9MPdJGNfvBfvgqKxO8Aqf1qQo= +=kN0n +-----END PGP PUBLIC KEY BLOCK----- diff --git a/spec/gnupg/openpgp-revocs.d/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.rev b/spec/gnupg/openpgp-revocs.d/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.rev new file mode 100644 index 0000000..1318ace --- /dev/null +++ b/spec/gnupg/openpgp-revocs.d/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.rev @@ -0,0 +1,28 @@ +This is a revocation certificate for the OpenPGP key: + +pub ed25519 2022-09-06 [SC] [expires: 2024-09-05] + BB5B4E7CCBBAC3CC0E249E683B4327815B714412 +uid wkd-srv-test@example.org + +A revocation certificate is a kind of "kill switch" to publicly +declare that a key shall not anymore be used. It is not possible +to retract such a revocation certificate once it has been published. + +Use it to revoke this key in case of a compromise or loss of +the secret key. However, if the secret key is still accessible, +it is better to generate a new revocation certificate and give +a reason for the revocation. For details see the description of +of the gpg command "--generate-revocation" in the GnuPG manual. + +To avoid an accidental use of this file, a colon has been inserted +before the 5 dashes below. Remove this colon with a text editor +before importing and publishing this revocation certificate. + +:-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: This is a revocation certificate + +iHgEIBYKACAWIQS7W058y7rDzA4knmg7QyeBW3FEEgUCYxeySwIdAAAKCRA7QyeB +W3FEEpWkAQCVNi68ULXsyb9aMdWa0buoPV3TuoyDBNKWPPZNokAv3QD+Py4F5xQt +eLo7w1YF+/hDHhCG0cGnztZUx+o2cKzMsQc= +=KWWb +-----END PGP PUBLIC KEY BLOCK----- diff --git a/spec/gnupg/private-keys-v1.d/3BCB7CC85B537B8E4355DB073112EF9ED78E85AD.key b/spec/gnupg/private-keys-v1.d/3BCB7CC85B537B8E4355DB073112EF9ED78E85AD.key new file mode 100644 index 0000000..1f34ba9 --- /dev/null +++ b/spec/gnupg/private-keys-v1.d/3BCB7CC85B537B8E4355DB073112EF9ED78E85AD.key @@ -0,0 +1,5 @@ +Created: 20220906T203434 +Key: (private-key (ecc (curve Curve25519)(flags djb-tweak)(q + #403EBFF1F9BDA38C1BC887F21809E314F4FB550E5497E30D9DD06BD78A94B84A7D#) + (d #78C74684B6406EA4F04C6A6E487672E2B66D6CF241F039C676BA13257A126510#) + )) diff --git a/spec/gnupg/private-keys-v1.d/79D43376C9F835C6CF705976E764EB1F4F5F6374.key b/spec/gnupg/private-keys-v1.d/79D43376C9F835C6CF705976E764EB1F4F5F6374.key new file mode 100644 index 0000000..e1f882d --- /dev/null +++ b/spec/gnupg/private-keys-v1.d/79D43376C9F835C6CF705976E764EB1F4F5F6374.key @@ -0,0 +1,5 @@ +Created: 20220906T204907 +Key: (private-key (ecc (curve Curve25519)(flags djb-tweak)(q + #4047AC7B80FA9ACD4AB097D5CF7D853816E5F4F6827AAE184792536D7E5EECF85A#) + (d #5E4DAC728C34688E58EA652881E8695A699ADC3FB2DC22CB12F5886E8FC16F30#) + )) diff --git a/spec/gnupg/private-keys-v1.d/C38BFA1C8FE8EEFE856D83BC2DD5B6306DC574CD.key b/spec/gnupg/private-keys-v1.d/C38BFA1C8FE8EEFE856D83BC2DD5B6306DC574CD.key new file mode 100644 index 0000000..7ae3c89 --- /dev/null +++ b/spec/gnupg/private-keys-v1.d/C38BFA1C8FE8EEFE856D83BC2DD5B6306DC574CD.key @@ -0,0 +1,5 @@ +Created: 20220906T203434 +Key: (private-key (ecc (curve Ed25519)(flags eddsa)(q + #40B88228F48C83DE60BCE05CC6E264989546D6B062A8B05E74E48005BE9F8244C1#) + (d #4B7B22D1B8E86B5B3B7414CBF5C4EFB5D73384D70B9B27ECE407EE29A2FE2BA9#) + )) diff --git a/spec/gnupg/private-keys-v1.d/FF779A201FE3D50D2C651A21438F91EEFC3B86D2.key b/spec/gnupg/private-keys-v1.d/FF779A201FE3D50D2C651A21438F91EEFC3B86D2.key new file mode 100644 index 0000000..b027844 --- /dev/null +++ b/spec/gnupg/private-keys-v1.d/FF779A201FE3D50D2C651A21438F91EEFC3B86D2.key @@ -0,0 +1,5 @@ +Created: 20220906T204907 +Key: (private-key (ecc (curve Ed25519)(flags eddsa)(q + #4006CD8C18C289DB0B1FDE611F2252198827E7DE92D564F24D83F2BD0186A046AB#) + (d #D487AD66A4FF5D2328863ECD0BCD46C5A157E5FA5B608B029236EA7432BE8E71#) + )) diff --git a/spec/gnupg/pubring.kbx b/spec/gnupg/pubring.kbx index 36483cca34c1469700d928de7e14d55fdd1a904d..c45d6892b52ac52050fea7c2344d7a79163a6493 100644 GIT binary patch delta 1162 zcmeyMdBAwX2azZS2Bst?Mg|b5V_;ysz`(>H)BR(9h2*Bwmue5kCe>`25#Y1Fw%+oPK-wXR!Q-AWCjJ@+WDoX=Sa6t)4$0RbZe50E?z#B!4t@XG`zi?3$}iO(=* zNfuvsON>*Sjgf`%mODQqBfGdNGhH5BYF{1L!FHA{ zT&nJ=%QEf2^=up1z@A{(VF3;SAuezTOb=yb1qFfK{*OQRF7A;&(f&z-^RdX6-=TaV z(;xHBy^wvqYswCZP1ScRE}C2E z^QFeb{92;RD=8K)@vyJ5ED_RAsu=$JcDwXH*GX9&!ppVJJWfFI(|6`6O4X0Me77zy zDv1%|ggL$^VrO?n%>xIA&U@80AxHb;HpvUf0h3N1Qqs8tN;loUsK)iYt~t>4b!iluWhvud-~;DQ`I^N_esIIb#ZTgM1hl0Jt7_9^By=E!CbFh z!pJby?Q+)2_lbL|q}B+Wnz7C`ct+p#Elt+{MYX*Sa0YQ*W@Na=+J9~Lf|)F~FP7}p ynPxj#Y16&V+sqp;#u;cIcre=png_(v8Lphk>V40dagH^}>e_csE9tEw0u}(&3yX*V delta 7 OcmX?L{6TZW2N3`d(F4H% diff --git a/spec/gnupg/pubring.kbx~ b/spec/gnupg/pubring.kbx~ index 3cba26d4bd6c5834f24abf67b1c59a6804543f6f..c113b8ff68c0e7c8522b92b3d29bb339f71dbe39 100644 GIT binary patch delta 909 zcmeyMc}Z`B5*Oov&B|PAjEpIhy|}{}*G={jRIShc{U}US|L09EPxk)~mIq8Wy?Xu3 zj>qR`7xPKZ$68B;mv+2AS2bhLiHzXgixXaLJT`H@+s{|i+k^ctefwNmIm7PEgKPiJ z>;7d@;J1C*x+jHad!E&qgZ8iZ8%}X-F=0CPi*e3Y{m*{i#Vl4lX}|sFwdB-;yZgk> zt=+!du*AaEDW`t!SC#*hI4blw*UwaxwBYmH5wT?5)jRGxU+vqP-pD5quesHK>YEVN zrS+_jj#_=WYv?fHy?|9>MT4A~cvD)_;dTAKhZ{1}STBZ7Ns+r6@}pPY`-RMklsymG zH4n*^%ZXBe`|YK(d5^A=Q5m3`TXry$+zE1Zl@K*Z%_KCCzK{U%jwnLsUQAq zKP_z3wtKGUpFEB7M$aMdhUm>~a^rhOvu}L*sW(4@GFZlzMR{$xh{PyT3ORejcR<}}v zXwN;$E9Y}o0)=frazMbyzyl-?1F_uX1^hC>$>QsoLEZ?wjA_a&QCCETHQlu4C9DmtT^R zTBJMqzkozNREh~?HUmS)Ocr4=E(S+2MHXysXJrD}%_Pmt%E^2voq?5`lY^a!Nr{O~ zl#7#tn~6n?iJ6f}j-5%ofq{!t0OsQ(nhgKbq;_7DUeY)7i0?_kptt{K=Y5;;Rf|LW zdi^o|h4!k&OW!jxobUSdNGhH5BYF{1L!FHA{T&nJ=%QEf2^=!bH0C|F8 zhXptUgt)*VFg=u!6%+(^`#=8NySPXCMEfTR&c`BOeuwgfOn=Nf_d@pdt|>dbYMB{1 z*gGm%B!IqCK=NHZa@4@Q$Aj$pJ+ch{)GjStG*#cNxM*&r&zBk#^J|GNucTPK#KXSI zvP4Kfsbcu=+wIc-Tqk992rt(<^Ed&;Pv4oRC{;i1^4+?;s3b;+6Xy7yh@IUPH4hvd RI`382gdFW>*(5I@2LP`Fb6EfY delta 337 zcmV-X0j~biF7PR^A_@T@vm**70ReN9N()y36O&94C4X!C;#L(e`Ps612l)Y(jHqNY z>CG87TYZh7E9O~$s<{f5U8$M`G1kRx$bTCG=F})f!VNG7>cE30k8|Af(DeS4K|1`2 zL`_(%mM%!U(EXL}C`g|ZawyWiU#n94h6sy6W1PHmi0EpmO&;>b0e6BLYLQ?WJhTIviUfeJ*h z@3^Zcrpi0DHM;xciWE#sOBD9zChLok6^RB9phe^9>NIw^ZyhDDce}r~u03NW+3}yK zc7Vv8JyPXj&4Jx|Wrwm_amD;cKws9?ogax7MH0!Ey|t1PkH+ITi*tyGum%7Cv)2q( j0vP~jaAyF+g}htsWt{su-bW4$SQwcAC~W+*Mh~h5{vo1z diff --git a/spec/gnupg/trustdb.gpg b/spec/gnupg/trustdb.gpg index c611b4501e06e0792da074fea6277eb4afc6b4d3..5399cad2432975c8f3c9e189954901a4d7bd385e 100644 GIT binary patch delta 206 zcmeytHG@ZlF})z2nVFH5k%@sJS$tnY+AYyr69a`es^>8=Do?b#%&59KiZHU=pa1$~`_et^O!it( zbqHk)JPf;|{c29{I(&vtWnPB0vwCB6p^Feyof<+RF9V0#vcne?XV=bsRL+oesnI{< KWr2`1rhxzoZZ%;5 delta 46 zcmV+}0MY-L4EPEZ0cUV$0|NmC0RjL3T-h?pV4Il}kx>(|CvF0fU=y)W{Q;3+3zM(} E$T_ exc + result = exc + end + + key_file = WkdSrv::Key.send(:wkd_filepath, emailaddress) + + expect(result).to be_a(RuntimeError) + expect(result.to_s).to eql("Error: key material contains no key matching the given address!") + expect(File.exist?(key_file)).to be(false) + end + + it "returns an error if input contains no matching key with exact address" do + emailaddress = 'wkd-srv-test@example.org' + key_material = File.read('fixtures/keys/BB5B4E7CCBBAC3CC0E249E683B4327815B714412.pub') + result = nil + + begin + result = WkdSrv::Key.create(emailaddress, key_material) + rescue => exc + result = exc + end + + key_file = WkdSrv::Key.send(:wkd_filepath, emailaddress) + + expect(result).to be_a(RuntimeError) + expect(result.to_s).to eql("Error: key material contains no key matching the given address!") + expect(File.exist?(key_file)).to be(false) + end + it "returns an error if the given email address is not a valid email address" do expect { WkdSrv::Key.create('something', 'bla') @@ -97,7 +146,7 @@ describe WkdSrv::Key do end expect(result).to be_a(RuntimeError) - expect(result.to_s).to eql('Error: key material contains no key matching the given address!') + expect(result.to_s).to eql('Error: key material contains no key!') end it "works with keys with UTF-8 in uid" do -- GitLab