1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
|
module U2F
##
# Representation of a U2F registration response.
# See chapter 4.3:
# http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
class RegisterResponse
attr_accessor :client_data, :client_data_json, :registration_data_raw
PUBLIC_KEY_OFFSET = 1
PUBLIC_KEY_LENGTH = 65
KEY_HANDLE_LENGTH_LENGTH = 1
KEY_HANDLE_LENGTH_OFFSET = PUBLIC_KEY_OFFSET + PUBLIC_KEY_LENGTH
KEY_HANDLE_OFFSET = KEY_HANDLE_LENGTH_OFFSET + KEY_HANDLE_LENGTH_LENGTH
def self.load_from_json(json)
# TODO: validate
data = JSON.parse(json)
if data['errorCode'] && data['errorCode'] > 0
fail RegistrationError, :code => data['errorCode']
end
instance = new
instance.client_data_json =
::U2F.urlsafe_decode64(data['clientData'])
instance.client_data =
ClientData.load_from_json(instance.client_data_json)
instance.registration_data_raw =
::U2F.urlsafe_decode64(data['registrationData'])
instance
end
##
# The attestation certificate in Base64 encoded X.509 DER format
def certificate
Base64.strict_encode64(parsed_certificate.to_der)
end
##
# The parsed attestation certificate
def parsed_certificate
OpenSSL::X509::Certificate.new(certificate_bytes)
end
##
# Length of the attestation certificate
def certificate_length
parsed_certificate.to_der.bytesize
end
##
# Returns the key handle from registration data, URL safe base64 encoded
def key_handle
::U2F.urlsafe_encode64(key_handle_raw)
end
def key_handle_raw
registration_data_raw.byteslice(KEY_HANDLE_OFFSET, key_handle_length)
end
##
# Returns the length of the key handle, extracted from the registration data
def key_handle_length
registration_data_raw.byteslice(KEY_HANDLE_LENGTH_OFFSET).unpack('C').first
end
##
# Returns the public key, extracted from the registration data
def public_key
# Base64 encode without linefeeds
Base64.strict_encode64(public_key_raw)
end
def public_key_raw
registration_data_raw.byteslice(PUBLIC_KEY_OFFSET, PUBLIC_KEY_LENGTH)
end
##
# Returns the signature, extracted from the registration data
def signature
registration_data_raw.byteslice(
(KEY_HANDLE_OFFSET + key_handle_length + certificate_length)..-1)
end
##
# Verifies the registration data against the app id
def verify(app_id)
# Chapter 4.3 in
# http://fidoalliance.org/specs/fido-u2f-raw-message-formats-v1.0-rd-20141008.pdf
data = [
"\x00",
::U2F::DIGEST.digest(app_id),
::U2F::DIGEST.digest(client_data_json),
key_handle_raw,
public_key_raw
].join
begin
parsed_certificate.public_key.verify(::U2F::DIGEST.new, signature, data)
rescue OpenSSL::PKey::PKeyError
false
end
end
private
def certificate_bytes
base_offset = KEY_HANDLE_OFFSET + key_handle_length
registration_data_raw.byteslice(base_offset..-1)
end
end
end
|