Class: U2F::U2F

Inherits:
Object
  • Object
show all
Defined in:
lib/u2f/u2f.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app_id) ⇒ U2F

  • Args:

    • app_id

      An application (facet) ID string



8
9
10
# File 'lib/u2f/u2f.rb', line 8

def initialize(app_id)
  @app_id = app_id
end

Instance Attribute Details

#app_idObject

Returns the value of attribute app_id.



3
4
5
# File 'lib/u2f/u2f.rb', line 3

def app_id
  @app_id
end

Class Method Details

.public_key_pem(key) ⇒ Object

Convert a binary public key to PEM format

  • Args:

    • key

      Binary public key

  • Returns:

    • A base64 encoded public key String in PEM format

  • Raises:

    • PublicKeyDecodeError

      if the key argument is incorrect



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/u2f/u2f.rb', line 142

def self.public_key_pem(key)
  fail PublicKeyDecodeError unless key.bytesize == 65 && key.byteslice(0) == "\x04"
  # http://tools.ietf.org/html/rfc5480
  der = OpenSSL::ASN1::Sequence([
    OpenSSL::ASN1::Sequence([
      OpenSSL::ASN1::ObjectId('1.2.840.10045.2.1'),  # id-ecPublicKey
      OpenSSL::ASN1::ObjectId('1.2.840.10045.3.1.7') # secp256r1
    ]),
    OpenSSL::ASN1::BitString(key)
  ]).to_der

  pem = "-----BEGIN PUBLIC KEY-----\r\n" +
        Base64.strict_encode64(der).scan(/.{1,64}/).join("\r\n") +
        "\r\n-----END PUBLIC KEY-----"
  pem
end

Instance Method Details

#authenticate!(challenge, response, registration_public_key, registration_counter) ⇒ Object

Authenticate a response from the U2F device

  • Args:

    • challenges

      Array of challenge strings

    • response

      Response from the U2F device as a SignResponse object

    • registration_public_key

      Public key of the registered U2F device as binary string

    • registration_counter

      Integer with the current counter value of the registered device.

  • Raises:

    • NoMatchingRequestError

      if the challenge in the response doesn’t match any of the provided ones.

    • ClientDataTypeError

      if the response is of the wrong type

    • AuthenticationFailedError

      if the authentication failed

    • UserNotPresentError

      if the user wasn’t present during the authentication

    • CounterTooLowError

      if there is a counter mismatch between the registered one and the one in the response.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/u2f/u2f.rb', line 44

def authenticate!(challenge, response, registration_public_key,
                  registration_counter)

  # TODO: check that it's the correct key_handle as well
  unless challenge == response.client_data.challenge
    fail NoMatchingRequestError
  end

  fail ClientDataTypeError unless response.client_data.authentication?

  pem = U2F.public_key_pem(registration_public_key)

  fail AuthenticationFailedError unless response.verify(app_id, pem)

  fail UserNotPresentError unless response.user_present?

  unless response.counter > registration_counter
    unless response.counter == 0 && registration_counter == 0
      fail CounterTooLowError
    end
  end
end

#authentication_requests(key_handles) ⇒ Object

Generate data to be sent to the U2F device before authenticating

  • Args:

    • key_handles

      Array of previously registered U2F key handles

  • Returns:

    • An Array of SignRequest objects



21
22
23
24
25
26
# File 'lib/u2f/u2f.rb', line 21

def authentication_requests(key_handles)
  key_handles = [key_handles] unless key_handles.is_a? Array
  key_handles.map do |key_handle|
    SignRequest.new(key_handle)
  end
end

#challengeObject

Generates a 32 byte long random U2F challenge

  • Returns:

    • Base64 urlsafe encoded challenge



73
74
75
# File 'lib/u2f/u2f.rb', line 73

def challenge
  ::U2F.urlsafe_encode64(SecureRandom.random_bytes(32))
end

#register!(challenges, response) ⇒ Object

Authenticate the response from the U2F device when registering

  • Args:

    • challenges

      Array of challenge strings

    • response

      Response of the U2F device as a RegisterResponse object

  • Returns:

    • A Registration object

  • Raises:

    • UnmatchedChallengeError

      if the challenge in the response doesn’t match any of the provided ones.

    • ClientDataTypeError

      if the response is of the wrong type

    • AttestationSignatureError

      if the registration failed



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/u2f/u2f.rb', line 103

def register!(challenges, response)
  challenges = [challenges] unless challenges.is_a? Array
  challenge = challenges.detect do |chg|
    chg == response.client_data.challenge
  end

  fail UnmatchedChallengeError unless challenge

  fail ClientDataTypeError unless response.client_data.registration?

  # Validate public key
  U2F.public_key_pem(response.public_key_raw)

  # TODO:
  # unless U2F.validate_certificate(response.certificate_raw)
  #   fail AttestationVerificationError
  # end

  fail AttestationSignatureError unless response.verify(app_id)

  registration = Registration.new(
    response.key_handle,
    response.public_key,
    response.certificate
  )
  registration
end

#registration_requestsObject

Generate data to be used when registering a U2F device

  • Returns:

    • An Array of RegisterRequest objects



83
84
85
86
# File 'lib/u2f/u2f.rb', line 83

def registration_requests
  # TODO: generate a request for each supported version
  [RegisterRequest.new(challenge)]
end