diff --git a/lib/ssh_data/public_key.rb b/lib/ssh_data/public_key.rb index ea5dcdb..4cfc3b8 100644 --- a/lib/ssh_data/public_key.rb +++ b/lib/ssh_data/public_key.rb @@ -20,6 +20,14 @@ module SSHData ALGO_ED25519, ALGO_SKECDSA256, ALGO_SKED25519 ] + DEFAULT_SK_VERIFY_OPTS = { + user_presence_required: true, + user_verification_required: false + } + + SK_FLAG_USER_PRESENCE = 0b001 + SK_FLAG_USER_VERIFICATION = 0b100 + # Parse an OpenSSH public key in authorized_keys format (see sshd(8) manual # page). # @@ -78,6 +86,7 @@ module SSHData end require "ssh_data/public_key/base" +require "ssh_data/public_key/security_key" require "ssh_data/public_key/rsa" require "ssh_data/public_key/dsa" require "ssh_data/public_key/ecdsa" diff --git a/lib/ssh_data/public_key/security_key.rb b/lib/ssh_data/public_key/security_key.rb new file mode 100644 index 0000000..0840060 --- /dev/null +++ b/lib/ssh_data/public_key/security_key.rb @@ -0,0 +1,30 @@ +module SSHData + module PublicKey + module SecurityKey + def build_signing_blob(application, signed_data, signature) + read = 0 + sig_algo, raw_sig, signature_read = Encoding.decode_signature(signature) + read += signature_read + sk_flags, sk_flags_read = Encoding.decode_uint8(signature, read) + read += sk_flags_read + counter, counter_read = Encoding.decode_uint32(signature, read) + read += counter_read + + if read != signature.bytesize + raise DecodeError, "unexpected trailing data" + end + + application_hash = OpenSSL::Digest::SHA256.digest(application) + message_hash = OpenSSL::Digest::SHA256.digest(signed_data) + + blob = + application_hash + + Encoding.encode_uint8(sk_flags) + + Encoding.encode_uint32(counter) + + message_hash + + [sig_algo, raw_sig, sk_flags, blob] + end + end + end +end diff --git a/lib/ssh_data/public_key/skecdsa.rb b/lib/ssh_data/public_key/skecdsa.rb index cca751c..9391bf4 100644 --- a/lib/ssh_data/public_key/skecdsa.rb +++ b/lib/ssh_data/public_key/skecdsa.rb @@ -1,6 +1,7 @@ module SSHData module PublicKey class SKECDSA < ECDSA + include SecurityKey attr_reader :application OPENSSL_CURVE_NAME_FOR_CURVE = { @@ -34,34 +35,25 @@ module SSHData ) end - def verify(signed_data, signature) + def verify(signed_data, signature, **opts) + opts = DEFAULT_SK_VERIFY_OPTS.merge(opts) + read = 0 - sig_algo, raw_sig, signature_read = Encoding.decode_signature(signature) - read += signature_read - sk_flags, sk_flags_read = Encoding.decode_uint8(signature, read) - read += sk_flags_read - counter, counter_read = Encoding.decode_uint32(signature, read) - read += counter_read - - if read != signature.bytesize - raise DecodeError, "unexpected trailing data" - end - + sig_algo, raw_sig, sk_flags, blob = build_signing_blob(application, signed_data, signature) self.class.check_algorithm!(sig_algo, curve) - application_hash = OpenSSL::Digest::SHA256.digest(application) - message_hash = OpenSSL::Digest::SHA256.digest(signed_data) - - blob = - application_hash + - Encoding.encode_uint8(sk_flags) + - Encoding.encode_uint32(counter) + - message_hash - openssl_sig = self.class.openssl_signature(raw_sig) digest = DIGEST_FOR_CURVE[curve] - openssl.verify(digest.new, openssl_sig, blob) + result = openssl.verify(digest.new, openssl_sig, blob) + + if opts[:user_presence_required] && (sk_flags & SK_FLAG_USER_PRESENCE != SK_FLAG_USER_PRESENCE) + false + elsif opts[:user_verification_required] && (sk_flags & SK_FLAG_USER_VERIFICATION != SK_FLAG_USER_VERIFICATION) + false + else + result + end end def ==(other) diff --git a/lib/ssh_data/public_key/sked25519.rb b/lib/ssh_data/public_key/sked25519.rb index e31bfab..343f000 100644 --- a/lib/ssh_data/public_key/sked25519.rb +++ b/lib/ssh_data/public_key/sked25519.rb @@ -1,6 +1,7 @@ module SSHData module PublicKey class SKED25519 < ED25519 + include SecurityKey attr_reader :application def initialize(algo:, pk:, application:) @@ -23,38 +24,27 @@ module SSHData ) end - def verify(signed_data, signature) + def verify(signed_data, signature, **opts) self.class.ed25519_gem_required! - - read = 0 - sig_algo, raw_sig, signature_read = Encoding.decode_signature(signature) - read += signature_read - sk_flags, sk_flags_read = Encoding.decode_uint8(signature, read) - read += sk_flags_read - counter, counter_read = Encoding.decode_uint32(signature, read) - read += counter_read - - if read != signature.bytesize - raise DecodeError, "unexpected trailing data" - end + opts = DEFAULT_SK_VERIFY_OPTS.merge(opts) + sig_algo, raw_sig, sk_flags, blob = build_signing_blob(application, signed_data, signature) if sig_algo != self.class.algorithm_identifier raise DecodeError, "bad signature algorithm: #{sig_algo.inspect}" end - application_hash = OpenSSL::Digest::SHA256.digest(application) - message_hash = OpenSSL::Digest::SHA256.digest(signed_data) + result = begin + ed25519_key.verify(raw_sig, blob) + rescue Ed25519::VerifyError + false + end - blob = - application_hash + - Encoding.encode_uint8(sk_flags) + - Encoding.encode_uint32(counter) + - message_hash - - begin - ed25519_key.verify(raw_sig, blob) - rescue Ed25519::VerifyError + if opts[:user_presence_required] && (sk_flags & SK_FLAG_USER_PRESENCE != SK_FLAG_USER_PRESENCE) false + elsif opts[:user_verification_required] && (sk_flags & SK_FLAG_USER_VERIFICATION != SK_FLAG_USER_VERIFICATION) + false + else + result end end diff --git a/lib/ssh_data/signature.rb b/lib/ssh_data/signature.rb index 3ff19f5..520dd64 100644 --- a/lib/ssh_data/signature.rb +++ b/lib/ssh_data/signature.rb @@ -70,7 +70,7 @@ module SSHData @signature = signature end - def verify(signed_data) + def verify(signed_data, **opts) key = public_key digest_algorithm = SUPPORTED_HASH_ALGORITHMS[@hash_algorithm] @@ -93,7 +93,11 @@ module SSHData Encoding.encode_string(@hash_algorithm) + Encoding.encode_string(message_digest) - key.verify(blob, @signature) + if key.class.include?(::SSHData::PublicKey::SecurityKey) + key.verify(blob, @signature, **opts) + else + key.verify(blob, @signature) + end end def public_key