diff --git a/.gitignore b/.gitignore index 180bf07d562cd9d8d715932ea83c65f71583d1a1..dc664f74a72f848b99c568cd44b3d99ff3d5ce78 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 3da3ea963043221604a679bd890068400bf43384..63b80876cb4834d4aa8e42021cc3aaa44771bb49 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 0000000000000000000000000000000000000000..73380642e1d819def9fc0508f0d0740be4f851f9 --- /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 0000000000000000000000000000000000000000..a93836e66a214154def034d58903029ad33ce4fa --- /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 0bbf3be54a6093c692d40925391281fbe8cd2d1b..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..ee2153e0de48a4821645c6c3091de205b634f4d6 --- /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 0000000000000000000000000000000000000000..1318ace50a197a4bd1728dbfe3549fce432fd90f --- /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 0000000000000000000000000000000000000000..1f34ba9bf12a7d1ec9599215aee7b2036822a5e5 --- /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 0000000000000000000000000000000000000000..e1f882de5ac878fa62144c7b00f06a939aa62db0 --- /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 0000000000000000000000000000000000000000..7ae3c8956e16d857c7542b71b40bf865dc705673 --- /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 0000000000000000000000000000000000000000..b0278442e524d44455c69f744d36747efb07da91 --- /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 Binary files a/spec/gnupg/pubring.kbx and b/spec/gnupg/pubring.kbx differ diff --git a/spec/gnupg/pubring.kbx~ b/spec/gnupg/pubring.kbx~ index 3cba26d4bd6c5834f24abf67b1c59a6804543f6f..c113b8ff68c0e7c8522b92b3d29bb339f71dbe39 100644 Binary files a/spec/gnupg/pubring.kbx~ and b/spec/gnupg/pubring.kbx~ differ diff --git a/spec/gnupg/trustdb.gpg b/spec/gnupg/trustdb.gpg index c611b4501e06e0792da074fea6277eb4afc6b4d3..5399cad2432975c8f3c9e189954901a4d7bd385e 100644 Binary files a/spec/gnupg/trustdb.gpg and b/spec/gnupg/trustdb.gpg differ diff --git a/spec/wkd-srv/api_spec.rb b/spec/wkd-srv/api_spec.rb index d2f3fcbecec4a6c63ee9c1552fb508df1e76c09e..1340625a0baa965512426f65ac6277692a4330b0 100644 --- a/spec/wkd-srv/api_spec.rb +++ b/spec/wkd-srv/api_spec.rb @@ -53,7 +53,7 @@ describe WkdSrv::Api do post_binary_data_as 'any', "/#{emailaddress}", key_material expect(last_response.status).to be(400) - expect(last_response.body).to eql('Error: key material contains no key matching the given address!') + expect(last_response.body).to eql('Error: key material contains no key!') expect(File.exist?(wkd_file)).to be(false) end @@ -62,7 +62,6 @@ describe WkdSrv::Api do key_material = File.read('fixtures/keys/161ABD3E62FF46A63667BF18F327AC8FCA412CA6.pub') wkd_file = WkdSrv::Key.send(:wkd_filepath, emailaddress) - post_binary_data_as 'any', "/#{emailaddress}", key_material expect(last_response.status).to be(400) @@ -82,6 +81,18 @@ describe WkdSrv::Api do expect(File.exist?(wkd_file)).to be(true) end + it 'should return a 201 status on success with plain email in uid' do + emailaddress = 'other-wkd-srv-test@example.org' + key_material = File.read('fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.pub') + wkd_file = WkdSrv::Key.send(:wkd_filepath, emailaddress) + + post_binary_data_as 'any', "/#{emailaddress}", key_material + + expect(last_response.status).to be(201) + expect(last_response.body).to eql('') + expect(File.exist?(wkd_file)).to be(true) + end + end end diff --git a/spec/wkd-srv/key_spec.rb b/spec/wkd-srv/key_spec.rb index ab2f69cf2cf9086507904088ad4e7559a65882b3..eabf95b6219e409813e008f057c736f2cf6a91b9 100644 --- a/spec/wkd-srv/key_spec.rb +++ b/spec/wkd-srv/key_spec.rb @@ -47,6 +47,18 @@ describe WkdSrv::Key do expect(File.exist?(key_file)).to be(true) end + it "writes a distincly matching key to the WKD directory with a plain email as uid" do + emailaddress = 'other-wkd-srv-test@example.org' + key_material = File.read('fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.pub') + + result = WkdSrv::Key.create(emailaddress, key_material) + + key_file = WkdSrv::Key.send(:wkd_filepath, emailaddress) + + expect(result).to be(true) + expect(File.exist?(key_file)).to be(true) + end + it "returns an error if input contains more than one distincly matching key" do emailaddress = 'wkd-srv-test@example.org' key_material = File.read("fixtures/keys/#{emailaddress}.pub") @@ -61,7 +73,7 @@ describe WkdSrv::Key do key_file = WkdSrv::Key.send(:wkd_filepath, emailaddress) expect(result).to be_a(RuntimeError) - expect(result.to_s).to eql("Error: key material contains more than one key matching the given address!") + expect(result.to_s).to eql("Error: key material contains more than one key!") expect(File.exist?(key_file)).to be(false) end @@ -83,6 +95,43 @@ describe WkdSrv::Key do expect(File.exist?(key_file)).to be(false) end + it "returns an error if input contains no matching key using a plain email uid" do + emailaddress = 'wkd-srv-test@example.org' + # key has other-wkd-srv-test@example.org + key_material = File.read('fixtures/keys/8BF89F7819B2CBD27DC35D627CB498504CAF74FC.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 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