Class: Vault

Inherits:
Object
  • Object
show all
Includes:
Crypto, Rights
Defined in:
lib/vault.rb

Overview

The Vault class represents a vault stored on a tag. It implements all operations that can be performed on the vault.

A vault stores credentials as [name, login, pass, hmac] quadruplets. When we say “a credential” we refer to this quadruplet. The name is ciphered with AES. The later three elements are ciphered together using AES. The three first elements are padded with zeroes to attain their mandated length (see constants). The HMAC is computed on unpadded (name || login || pass). The three keys are derives from the vault password using PKBDF2, altough they use different salt. All the salt include the tag UID, and the salt for the ciphering of the last three elements include the credential name.

For a credential, 112 bytes of data are stored of memory. This is a multiple of 8, so no padding is required in AES.

When a vault object is initialized, it scans the tag to get a map from credential names to the file where the credential is stored. It also builds lists of unused applications ids and of unused file ids on existing applications.

Defined Under Namespace

Classes: CredentialError

Constant Summary collapse

ZERO_KEY =

This is the default tag master key, and the initial key for new applications.

Array.new(16, 0).pack('C*')
FILE_RIGHTS =

These are the access rights we use when creating a new file.

FileAccess.new(
read: 0, write: 0, rw: 0, change: 0, com_set: CS_CIPHERED)
TAG_KEY_SETTINGS =

These are the key settings for the tag master key. This is set when creating the vault.

KeySettings.new(change_key: 0x0,
master_change: 1, auth_get: 1, auth_create: 1, settings_change: 1)
VAULT_KEY_SETTINGS =

These are the key settings for new applications.

KeySettings.new(
master_change: 1, auth_get: 0, auth_create: 0, settings_change: 0)
NB_FILES =

Number of files that a DESFire application can hold.

15
NAME_LEN =

The length of the name of a credential in the tag’s memory. This is the maximum length for a name, and a padding of nul characters is added to reach this length if needed.

16
LOGIN_LEN =

The length of the login in a credential in the tag’s memory. This is the maximum length for a login, and a padding of nul characters is added to reach this length if needed.

32
PASS_LEN =

The length of the password in a credential in the tag’s memory. This is the maximum length for a password, and a padding of nul characters is added to reach this length if needed.

32
TOTAL_LEN =

The total length on a credential in the tag’s memory.

NAME_LEN + LOGIN_LEN + PASS_LEN + HMAC_LEN
TAG_CHANGED_ERROR =

Raised if the tag contents changed while the program was running.

'Tag content changed while the program was running.'
TAG_FULL_ERROR =

Raised if the tag memory becomes full.

'Tag full, remove some credentials to be able to add other.'
UNKNOWN_NAME_ERROR =

Raised when we are supplied an unknown credential name.

'The credential name is unknown. Use "list" to view existing names.'
AUTHENTICATION_ERROR =

Raised if we are supplied an incorrect vault or tag password.

'The supplied password is incorrect, please try again.'
TEMPER_ERROR =

Raised if we detect that the tag memory has been tempered with.

'The card has been tempered with (invalid signature).'
NAME_IN_USE_TEXT =

Text used for credential errors where the name is already used.

'Credential name already in use, choose another one.'

Constants included from Rights

Rights::CHANGED_KEY, Rights::CS_CIPHERED, Rights::CS_PLAIN, Rights::CS_PLAIN_MAC, Rights::DENY_ACCESS, Rights::FREE_ACCESS, Rights::IMMUTABLE

Constants included from Crypto

Crypto::DES_BLEN, Crypto::HMAC_LEN, Crypto::ZERO_IV

Instance Method Summary collapse

Methods included from Crypto

#aes_decipher, #aes_encipher, #decipher_receive, #decipher_send, #derive_desfire_key, #derive_key, #encipher_receive, #encipher_send, #hmac

Methods included from BytesManipulation

#bs_rotate, #bs_xor, #desfire_crc, #to_hex_array, #to_le_array

Instance Method Details

#add(name, login, pass) ⇒ Object

Adds a new credential to the vault.

Raises:



138
139
140
141
142
143
144
145
# File 'lib/vault.rb', line 138

def add(name, , pass)
  ensure_free_file(@free_files, @free_apps, @key)
  aid, fid = @free_files.first
  raise CredentialError.new(NAME_IN_USE_TEXT)  unless @locations[name] == nil
  write_credentials(name, , pass, aid, fid)
  # Do this only after that potential exception have been thrown.
  @locations[name] = @free_files.shift
end

#authenticate(pass) ⇒ Object

Set the password and derive the key, then scans the card for available credentials (see scan()).



168
169
170
171
172
173
174
175
# File 'lib/vault.rb', line 168

def authenticate(pass)
  key = derive_desfire_key(pass, @uid)
  authentication { @conn.select_app_auth(1, key) }
  @pass = pass
  @key  = key
  @conn.select_app(0)
  scan()
end

#authenticated?Boolean

Indicate if we know the key to access the card.

Returns:

  • (Boolean)


185
186
187
# File 'lib/vault.rb', line 185

def authenticated?
  return @pass != nil
end

#credential(name) ⇒ Object

Returns the [login, pass] from the named credential.

Raises:



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/vault.rb', line 124

def credential(name)
  _ ,_ ,content = get_location(name)
  triplet_aes = content.byteslice(NAME_LEN, LOGIN_LEN + PASS_LEN + HMAC_LEN)
  triplet = aes_decipher(triplet_aes, derive_aes_key(name))

   = triplet.byteslice(0, LOGIN_LEN).tr("\x00", '')
  pass  = triplet.byteslice(LOGIN_LEN, PASS_LEN).tr("\x00", '')
  hmac  = triplet.byteslice(LOGIN_LEN + PASS_LEN, HMAC_LEN)

  raise TEMPER_ERROR if hmac != hmac(name +   + pass, derive_aes_key('hmac'))
  return [, pass]
end

#credentials_namesObject

Returns a list of credentials names.



119
120
121
# File 'lib/vault.rb', line 119

def credentials_names
  return @locations.each_key()
end

#deauthenticateObject

Forget all that is known about the card password, key and content.



178
179
180
181
182
# File 'lib/vault.rb', line 178

def deauthenticate
  @pass = nil
  @key = nil
  unscan()
end

#destroyObject

“Destroy” the Vault object by releasing the connection to the tag. Leaves the object in an unusable state.



191
192
193
194
# File 'lib/vault.rb', line 191

def destroy
  deauthenticate()
  @conn.disconnect
end

#edit(name, login, pass) ⇒ Object

Replace the login and password for the named credential.



148
149
150
151
# File 'lib/vault.rb', line 148

def edit(name, , pass)
  aid, fid = get_location(name)
  write_credentials(name, , pass, aid, fid)
end

#erase(tag_pass) ⇒ Object

Erase the vault from the tag and restore it to a pristine state (memory wiped, tag master key set to ZERO_KEY).



98
99
100
101
102
103
104
# File 'lib/vault.rb', line 98

def erase(tag_pass)
  deauthenticate()
  tag_key = derive_tag_key(tag_pass)
  authentication { @conn.select_app_auth(0, tag_key) }
  @conn.format()
  @conn.change_key(0, tag_key, ZERO_KEY)
end

#remove(name) ⇒ Object

Remove the named credential from the vault.



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/vault.rb', line 154

def remove(name)
  aid, fid, _ = get_location(name)
  @free_files << [aid, fid]
  @locations.delete(name)
  @conn.select_app_auth(aid, @key)
  # We do not remove the file, else the memory would be lost, instead
  # we overwrite the previous credential with zeroes.
  rand1 = Random.new(Random.new_seed).bytes(32)
  rand2 = Random.new(Random.new_seed).bytes(32)
  write_credentials('', rand1, rand2, aid, fid, true)
end

#reset(tag_pass, vault_pass) ⇒ Object

Reset the vault: erases the tag and creates a new vault on it using the given vault master password.



108
109
110
111
112
113
114
115
116
# File 'lib/vault.rb', line 108

def reset(tag_pass, vault_pass)
  deauthenticate()
  tag_key = derive_tag_key(tag_pass)
  vault_key = derive_key(vault_pass, @uid, 16)
  authentication { @conn.select_app_auth(0, tag_key) }
  @conn.format()
  @conn.change_key_settings(TAG_KEY_SETTINGS)
  ensure_free_file([], [1], vault_key) # create application 1
end