Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
O
OpenSK
Manage
Activity
Members
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Locked files
Deploy
Releases
Model registry
Analyze
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
This is an archived project. Repository and other project resources are read-only.
Show more breadcrumbs
koenigl2
OpenSK
Commits
dabbe386
Unverified
Commit
dabbe386
authored
5 years ago
by
kaczmarczyck
Committed by
GitHub
5 years ago
Browse files
Options
Downloads
Plain Diff
Merge pull request #75 from kaczmarczyck/HMAC-secret-extension
adding HMAC-secret support
parents
8de99f70
99ae5278
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
src/ctap/data_formats.rs
+95
-1
95 additions, 1 deletion
src/ctap/data_formats.rs
src/ctap/mod.rs
+267
-14
267 additions, 14 deletions
src/ctap/mod.rs
src/ctap/storage.rs
+2
-0
2 additions, 0 deletions
src/ctap/storage.rs
with
364 additions
and
15 deletions
src/ctap/data_formats.rs
+
95
−
1
View file @
dabbe386
...
@@ -220,6 +220,78 @@ impl TryFrom<&cbor::Value> for Extensions {
...
@@ -220,6 +220,78 @@ impl TryFrom<&cbor::Value> for Extensions {
}
}
}
}
impl
From
<
Extensions
>
for
cbor
::
Value
{
fn
from
(
extensions
:
Extensions
)
->
Self
{
cbor_map_btree!
(
extensions
.0
.into_iter
()
.map
(|(
key
,
value
)|
(
cbor_text!
(
key
),
value
))
.collect
())
}
}
impl
Extensions
{
#[cfg(test)]
pub
fn
new
(
extension_map
:
BTreeMap
<
String
,
cbor
::
Value
>
)
->
Self
{
Extensions
(
extension_map
)
}
pub
fn
has_make_credential_hmac_secret
(
&
self
)
->
Result
<
bool
,
Ctap2StatusCode
>
{
self
.0
.get
(
"hmac-secret"
)
.map
(
read_bool
)
.unwrap_or
(
Ok
(
false
))
}
pub
fn
get_assertion_hmac_secret
(
&
self
,
)
->
Option
<
Result
<
GetAssertionHmacSecretInput
,
Ctap2StatusCode
>>
{
self
.0
.get
(
"hmac-secret"
)
.map
(
GetAssertionHmacSecretInput
::
try_from
)
}
}
#[cfg_attr(any(test,
feature
=
"debug_ctap"
),
derive(Debug,
PartialEq))]
pub
struct
GetAssertionHmacSecretInput
{
pub
key_agreement
:
CoseKey
,
pub
salt_enc
:
Vec
<
u8
>
,
pub
salt_auth
:
Vec
<
u8
>
,
}
impl
TryFrom
<&
cbor
::
Value
>
for
GetAssertionHmacSecretInput
{
type
Error
=
Ctap2StatusCode
;
fn
try_from
(
cbor_value
:
&
cbor
::
Value
)
->
Result
<
Self
,
Ctap2StatusCode
>
{
let
input_map
=
read_map
(
cbor_value
)
?
;
let
cose_key
=
read_map
(
ok_or_missing
(
input_map
.get
(
&
cbor_unsigned!
(
1
)))
?
)
?
;
let
salt_enc
=
read_byte_string
(
ok_or_missing
(
input_map
.get
(
&
cbor_unsigned!
(
2
)))
?
)
?
;
let
salt_auth
=
read_byte_string
(
ok_or_missing
(
input_map
.get
(
&
cbor_unsigned!
(
3
)))
?
)
?
;
Ok
(
Self
{
key_agreement
:
CoseKey
(
cose_key
.clone
()),
salt_enc
,
salt_auth
,
})
}
}
#[cfg_attr(any(test,
feature
=
"debug_ctap"
),
derive(Debug,
PartialEq))]
pub
struct
GetAssertionHmacSecretOutput
(
Vec
<
u8
>
);
impl
From
<
GetAssertionHmacSecretOutput
>
for
cbor
::
Value
{
fn
from
(
message
:
GetAssertionHmacSecretOutput
)
->
cbor
::
Value
{
cbor_bytes!
(
message
.0
)
}
}
impl
TryFrom
<&
cbor
::
Value
>
for
GetAssertionHmacSecretOutput
{
type
Error
=
Ctap2StatusCode
;
fn
try_from
(
cbor_value
:
&
cbor
::
Value
)
->
Result
<
Self
,
Ctap2StatusCode
>
{
Ok
(
GetAssertionHmacSecretOutput
(
read_byte_string
(
cbor_value
)
?
))
}
}
// Even though options are optional, we can use the default if not present.
// Even though options are optional, we can use the default if not present.
#[cfg_attr(any(test,
feature
=
"debug_ctap"
),
derive(Debug,
PartialEq))]
#[cfg_attr(any(test,
feature
=
"debug_ctap"
),
derive(Debug,
PartialEq))]
pub
struct
MakeCredentialOptions
{
pub
struct
MakeCredentialOptions
{
...
@@ -314,6 +386,7 @@ pub struct PublicKeyCredentialSource {
...
@@ -314,6 +386,7 @@ pub struct PublicKeyCredentialSource {
pub
rp_id
:
String
,
pub
rp_id
:
String
,
pub
user_handle
:
Vec
<
u8
>
,
// not optional, but nullable
pub
user_handle
:
Vec
<
u8
>
,
// not optional, but nullable
pub
other_ui
:
Option
<
String
>
,
pub
other_ui
:
Option
<
String
>
,
pub
cred_random
:
Option
<
Vec
<
u8
>>
,
}
}
impl
From
<
PublicKeyCredentialSource
>
for
cbor
::
Value
{
impl
From
<
PublicKeyCredentialSource
>
for
cbor
::
Value
{
...
@@ -324,12 +397,17 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
...
@@ -324,12 +397,17 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
None
=>
cbor_null!
(),
None
=>
cbor_null!
(),
Some
(
other_ui
)
=>
cbor_text!
(
other_ui
),
Some
(
other_ui
)
=>
cbor_text!
(
other_ui
),
};
};
let
cred_random
=
match
credential
.cred_random
{
None
=>
cbor_null!
(),
Some
(
cred_random
)
=>
cbor_bytes!
(
cred_random
),
};
cbor_array!
{
cbor_array!
{
credential
.credential_id
,
credential
.credential_id
,
private_key
,
private_key
,
credential
.rp_id
,
credential
.rp_id
,
credential
.user_handle
,
credential
.user_handle
,
other_ui
,
other_ui
,
cred_random
,
}
}
}
}
}
}
...
@@ -341,7 +419,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
...
@@ -341,7 +419,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
use
cbor
::{
SimpleValue
,
Value
};
use
cbor
::{
SimpleValue
,
Value
};
let
fields
=
read_array
(
&
cbor_value
)
?
;
let
fields
=
read_array
(
&
cbor_value
)
?
;
if
fields
.len
()
!=
5
{
if
fields
.len
()
!=
6
{
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_INVALID_CBOR
);
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_INVALID_CBOR
);
}
}
let
credential_id
=
read_byte_string
(
&
fields
[
0
])
?
;
let
credential_id
=
read_byte_string
(
&
fields
[
0
])
?
;
...
@@ -357,6 +435,10 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
...
@@ -357,6 +435,10 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
Value
::
Simple
(
SimpleValue
::
NullValue
)
=>
None
,
Value
::
Simple
(
SimpleValue
::
NullValue
)
=>
None
,
cbor_value
=>
Some
(
read_text_string
(
cbor_value
)
?
),
cbor_value
=>
Some
(
read_text_string
(
cbor_value
)
?
),
};
};
let
cred_random
=
match
&
fields
[
5
]
{
Value
::
Simple
(
SimpleValue
::
NullValue
)
=>
None
,
cbor_value
=>
Some
(
read_byte_string
(
cbor_value
)
?
),
};
Ok
(
PublicKeyCredentialSource
{
Ok
(
PublicKeyCredentialSource
{
key_type
:
PublicKeyCredentialType
::
PublicKey
,
key_type
:
PublicKeyCredentialType
::
PublicKey
,
credential_id
,
credential_id
,
...
@@ -364,6 +446,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
...
@@ -364,6 +446,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
rp_id
,
rp_id
,
user_handle
,
user_handle
,
other_ui
,
other_ui
,
cred_random
,
})
})
}
}
}
}
...
@@ -993,6 +1076,7 @@ mod test {
...
@@ -993,6 +1076,7 @@ mod test {
rp_id
:
"example.com"
.to_string
(),
rp_id
:
"example.com"
.to_string
(),
user_handle
:
b"foo"
.to_vec
(),
user_handle
:
b"foo"
.to_vec
(),
other_ui
:
None
,
other_ui
:
None
,
cred_random
:
None
,
};
};
assert_eq!
(
assert_eq!
(
...
@@ -1005,6 +1089,16 @@ mod test {
...
@@ -1005,6 +1089,16 @@ mod test {
..
credential
..
credential
};
};
assert_eq!
(
PublicKeyCredentialSource
::
try_from
(
cbor
::
Value
::
from
(
credential
.clone
())),
Ok
(
credential
.clone
())
);
let
credential
=
PublicKeyCredentialSource
{
cred_random
:
Some
(
vec!
[
0x00
;
32
]),
..
credential
};
assert_eq!
(
assert_eq!
(
PublicKeyCredentialSource
::
try_from
(
cbor
::
Value
::
from
(
credential
.clone
())),
PublicKeyCredentialSource
::
try_from
(
cbor
::
Value
::
from
(
credential
.clone
())),
Ok
(
credential
)
Ok
(
credential
)
...
...
This diff is collapsed.
Click to expand it.
src/ctap/mod.rs
+
267
−
14
View file @
dabbe386
...
@@ -28,9 +28,9 @@ use self::command::{
...
@@ -28,9 +28,9 @@ use self::command::{
AuthenticatorMakeCredentialParameters
,
Command
,
AuthenticatorMakeCredentialParameters
,
Command
,
};
};
use
self
::
data_formats
::{
use
self
::
data_formats
::{
ClientPinSubCommand
,
CoseKey
,
PackedAttestationStatement
,
PublicKeyCredentialDescriptor
,
ClientPinSubCommand
,
CoseKey
,
GetAssertionHmacSecretInput
,
PackedAttestationStatement
,
PublicKeyCredentialSource
,
PublicKeyCredentialType
,
PublicKeyCredentialUserEntity
,
PublicKeyCredentialDescriptor
,
PublicKeyCredentialSource
,
PublicKeyCredentialType
,
SignatureAlgorithm
,
PublicKeyCredentialUserEntity
,
SignatureAlgorithm
,
};
};
use
self
::
hid
::
ChannelID
;
use
self
::
hid
::
ChannelID
;
use
self
::
key_material
::{
AAGUID
,
ATTESTATION_CERTIFICATE
,
ATTESTATION_PRIVATE_KEY
};
use
self
::
key_material
::{
AAGUID
,
ATTESTATION_CERTIFICATE
,
ATTESTATION_PRIVATE_KEY
};
...
@@ -81,9 +81,14 @@ const PIN_PADDED_LENGTH: usize = 64;
...
@@ -81,9 +81,14 @@ const PIN_PADDED_LENGTH: usize = 64;
// - 32 byte relying party ID hashed with SHA256,
// - 32 byte relying party ID hashed with SHA256,
// - 32 byte HMAC-SHA256 over everything else.
// - 32 byte HMAC-SHA256 over everything else.
pub
const
ENCRYPTED_CREDENTIAL_ID_SIZE
:
usize
=
112
;
pub
const
ENCRYPTED_CREDENTIAL_ID_SIZE
:
usize
=
112
;
// Set this bit when checking user presence.
const
UP_FLAG
:
u8
=
0x01
;
const
UP_FLAG
:
u8
=
0x01
;
// Set this bit when checking user verification.
const
UV_FLAG
:
u8
=
0x04
;
const
UV_FLAG
:
u8
=
0x04
;
// Set this bit when performing attestation.
const
AT_FLAG
:
u8
=
0x40
;
const
AT_FLAG
:
u8
=
0x40
;
// Set this bit when an extension is used.
const
ED_FLAG
:
u8
=
0x80
;
pub
const
TOUCH_TIMEOUT_MS
:
isize
=
30000
;
pub
const
TOUCH_TIMEOUT_MS
:
isize
=
30000
;
#[cfg(feature
=
"with_ctap1"
)]
#[cfg(feature
=
"with_ctap1"
)]
...
@@ -105,6 +110,63 @@ fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> boo
...
@@ -105,6 +110,63 @@ fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> boo
)
)
}
}
// Decrypts the HMAC secret salt(s) that were encrypted with the shared secret.
// The credRandom is used as a secret to HMAC those salts.
// The last step is to re-encrypt the outputs.
pub
fn
encrypt_hmac_secret_output
(
shared_secret
:
&
[
u8
;
32
],
salt_enc
:
&
[
u8
],
cred_random
:
&
[
u8
],
)
->
Result
<
Vec
<
u8
>
,
Ctap2StatusCode
>
{
if
salt_enc
.len
()
!=
32
&&
salt_enc
.len
()
!=
64
{
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
);
}
if
cred_random
.len
()
!=
32
{
// We are strict here. We need at least 32 byte, but expect exactly 32.
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
);
}
let
aes_enc_key
=
crypto
::
aes256
::
EncryptionKey
::
new
(
shared_secret
);
let
aes_dec_key
=
crypto
::
aes256
::
DecryptionKey
::
new
(
&
aes_enc_key
);
// The specification specifically asks for a zero IV.
let
iv
=
[
0
;
16
];
let
mut
cred_random_secret
=
[
0
;
32
];
cred_random_secret
.clone_from_slice
(
cred_random
);
// Initialization of 4 blocks in any case makes this function more readable.
let
mut
blocks
=
[[
0u8
;
16
];
4
];
let
block_len
=
salt_enc
.len
()
/
16
;
for
i
in
0
..
block_len
{
blocks
[
i
]
.copy_from_slice
(
&
salt_enc
[
16
*
i
..
16
*
(
i
+
1
)]);
}
cbc_decrypt
(
&
aes_dec_key
,
iv
,
&
mut
blocks
[
..
block_len
]);
let
mut
decrypted_salt1
=
[
0
;
32
];
decrypted_salt1
[
..
16
]
.clone_from_slice
(
&
blocks
[
0
]);
let
output1
=
hmac_256
::
<
Sha256
>
(
&
cred_random_secret
,
&
decrypted_salt1
[
..
]);
decrypted_salt1
[
16
..
]
.clone_from_slice
(
&
blocks
[
1
]);
for
i
in
0
..
2
{
blocks
[
i
]
.copy_from_slice
(
&
output1
[
16
*
i
..
16
*
(
i
+
1
)]);
}
if
block_len
==
4
{
let
mut
decrypted_salt2
=
[
0
;
32
];
decrypted_salt2
[
..
16
]
.clone_from_slice
(
&
blocks
[
2
]);
decrypted_salt2
[
16
..
]
.clone_from_slice
(
&
blocks
[
3
]);
let
output2
=
hmac_256
::
<
Sha256
>
(
&
cred_random_secret
,
&
decrypted_salt2
[
..
]);
for
i
in
0
..
2
{
blocks
[
i
+
2
]
.copy_from_slice
(
&
output2
[
16
*
i
..
16
*
(
i
+
1
)]);
}
}
cbc_encrypt
(
&
aes_enc_key
,
iv
,
&
mut
blocks
[
..
block_len
]);
let
mut
encrypted_output
=
Vec
::
with_capacity
(
salt_enc
.len
());
for
b
in
&
blocks
[
..
block_len
]
{
encrypted_output
.extend
(
b
);
}
Ok
(
encrypted_output
)
}
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
// We change the return value, since we don't need the bool.
// We change the return value, since we don't need the bool.
...
@@ -261,6 +323,7 @@ where
...
@@ -261,6 +323,7 @@ where
rp_id
:
String
::
from
(
""
),
rp_id
:
String
::
from
(
""
),
user_handle
:
vec!
[],
user_handle
:
vec!
[],
other_ui
:
None
,
other_ui
:
None
,
cred_random
:
None
,
})
})
}
}
...
@@ -324,10 +387,10 @@ where
...
@@ -324,10 +387,10 @@ where
user
,
user
,
pub_key_cred_params
,
pub_key_cred_params
,
exclude_list
,
exclude_list
,
extensions
,
options
,
options
,
pin_uv_auth_param
,
pin_uv_auth_param
,
pin_uv_auth_protocol
,
pin_uv_auth_protocol
,
..
}
=
make_credential_params
;
}
=
make_credential_params
;
if
let
Some
(
auth_param
)
=
&
pin_uv_auth_param
{
if
let
Some
(
auth_param
)
=
&
pin_uv_auth_param
{
...
@@ -362,6 +425,19 @@ where
...
@@ -362,6 +425,19 @@ where
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_ALGORITHM
);
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_ALGORITHM
);
}
}
let
use_hmac_extension
=
extensions
.map_or
(
Ok
(
false
),
|
e
|
e
.has_make_credential_hmac_secret
())
?
;
if
use_hmac_extension
&&
!
options
.rk
{
// The extension is actually supported, but we need resident keys.
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
);
}
let
cred_random
=
if
use_hmac_extension
{
Some
(
self
.rng
.gen_uniform_u8x32
()
.to_vec
())
}
else
{
None
};
let
ed_flag
=
if
use_hmac_extension
{
ED_FLAG
}
else
{
0
};
let
rp_id
=
rp
.rp_id
;
let
rp_id
=
rp
.rp_id
;
if
let
Some
(
exclude_list
)
=
exclude_list
{
if
let
Some
(
exclude_list
)
=
exclude_list
{
for
cred_desc
in
exclude_list
{
for
cred_desc
in
exclude_list
{
...
@@ -389,7 +465,7 @@ where
...
@@ -389,7 +465,7 @@ where
if
!
check_pin_auth
(
&
self
.pin_uv_auth_token
,
&
client_data_hash
,
&
pin_auth
)
{
if
!
check_pin_auth
(
&
self
.pin_uv_auth_token
,
&
client_data_hash
,
&
pin_auth
)
{
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_PIN_AUTH_INVALID
);
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_PIN_AUTH_INVALID
);
}
}
UP_FLAG
|
UV_FLAG
|
AT_FLAG
UP_FLAG
|
UV_FLAG
|
AT_FLAG
|
ed_flag
}
}
None
=>
{
None
=>
{
if
self
.persistent_store
.pin_hash
()
.is_some
()
{
if
self
.persistent_store
.pin_hash
()
.is_some
()
{
...
@@ -398,7 +474,7 @@ where
...
@@ -398,7 +474,7 @@ where
if
options
.uv
{
if
options
.uv
{
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_INVALID_OPTION
);
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_INVALID_OPTION
);
}
}
UP_FLAG
|
AT_FLAG
UP_FLAG
|
AT_FLAG
|
ed_flag
}
}
};
};
...
@@ -421,6 +497,7 @@ where
...
@@ -421,6 +497,7 @@ where
other_ui
:
user
other_ui
:
user
.user_display_name
.user_display_name
.map
(|
s
|
truncate_to_char_boundary
(
&
s
,
64
)
.to_string
()),
.map
(|
s
|
truncate_to_char_boundary
(
&
s
,
64
)
.to_string
()),
cred_random
,
};
};
self
.persistent_store
.store_credential
(
credential_source
)
?
;
self
.persistent_store
.store_credential
(
credential_source
)
?
;
random_id
random_id
...
@@ -441,6 +518,14 @@ where
...
@@ -441,6 +518,14 @@ where
None
=>
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR
),
None
=>
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR
),
};
};
auth_data
.extend
(
cose_key
);
auth_data
.extend
(
cose_key
);
if
use_hmac_extension
{
let
extensions
=
cbor_map!
{
"hmac-secret"
=>
true
,
};
if
!
cbor
::
write
(
extensions
,
&
mut
auth_data
)
{
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR
);
}
}
let
mut
signature_data
=
auth_data
.clone
();
let
mut
signature_data
=
auth_data
.clone
();
signature_data
.extend
(
client_data_hash
);
signature_data
.extend
(
client_data_hash
);
...
@@ -481,10 +566,10 @@ where
...
@@ -481,10 +566,10 @@ where
rp_id
,
rp_id
,
client_data_hash
,
client_data_hash
,
allow_list
,
allow_list
,
extensions
,
options
,
options
,
pin_uv_auth_param
,
pin_uv_auth_param
,
pin_uv_auth_protocol
,
pin_uv_auth_protocol
,
..
}
=
get_assertion_params
;
}
=
get_assertion_params
;
if
let
Some
(
auth_param
)
=
&
pin_uv_auth_param
{
if
let
Some
(
auth_param
)
=
&
pin_uv_auth_param
{
...
@@ -527,6 +612,15 @@ where
...
@@ -527,6 +612,15 @@ where
}
}
}
}
let
get_assertion_hmac_secret_input
=
match
extensions
{
Some
(
extensions
)
=>
extensions
.get_assertion_hmac_secret
()
.transpose
()
?
,
None
=>
None
,
};
if
get_assertion_hmac_secret_input
.is_some
()
&&
!
options
.up
{
// The extension is actually supported, but we need user presence.
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
);
}
// The user verification bit depends on the existance of PIN auth, whereas
// The user verification bit depends on the existance of PIN auth, whereas
// user presence is requested as an option.
// user presence is requested as an option.
let
mut
flags
=
match
pin_uv_auth_param
{
let
mut
flags
=
match
pin_uv_auth_param
{
...
@@ -551,6 +645,9 @@ where
...
@@ -551,6 +645,9 @@ where
if
options
.up
{
if
options
.up
{
flags
|
=
UP_FLAG
;
flags
|
=
UP_FLAG
;
}
}
if
get_assertion_hmac_secret_input
.is_some
()
{
flags
|
=
ED_FLAG
;
}
let
rp_id_hash
=
Sha256
::
hash
(
rp_id
.as_bytes
());
let
rp_id_hash
=
Sha256
::
hash
(
rp_id
.as_bytes
());
let
mut
decrypted_credential
=
None
;
let
mut
decrypted_credential
=
None
;
...
@@ -590,7 +687,36 @@ where
...
@@ -590,7 +687,36 @@ where
self
.increment_global_signature_counter
();
self
.increment_global_signature_counter
();
let
auth_data
=
self
.generate_auth_data
(
&
rp_id_hash
,
flags
);
let
mut
auth_data
=
self
.generate_auth_data
(
&
rp_id_hash
,
flags
);
// Process extensions.
if
let
Some
(
get_assertion_hmac_secret_input
)
=
get_assertion_hmac_secret_input
{
let
GetAssertionHmacSecretInput
{
key_agreement
,
salt_enc
,
salt_auth
,
}
=
get_assertion_hmac_secret_input
;
let
pk
:
crypto
::
ecdh
::
PubKey
=
CoseKey
::
try_into
(
key_agreement
)
?
;
let
shared_secret
=
self
.key_agreement_key
.exchange_x_sha256
(
&
pk
);
// HMAC-secret does the same 16 byte truncated check.
if
!
check_pin_auth
(
&
shared_secret
,
&
salt_enc
,
&
salt_auth
)
{
// Again, hard to tell what the correct error code here is.
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
);
}
let
encrypted_output
=
match
&
credential
.cred_random
{
Some
(
cr
)
=>
encrypt_hmac_secret_output
(
&
shared_secret
,
&
salt_enc
[
..
],
cr
)
?
,
// This is the case if the credential was not created with HMAC-secret.
None
=>
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
),
};
let
extensions
=
cbor_map!
{
"hmac-secret"
=>
encrypted_output
,
};
if
!
cbor
::
write
(
extensions
,
&
mut
auth_data
)
{
return
Err
(
Ctap2StatusCode
::
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR
);
}
}
let
mut
signature_data
=
auth_data
.clone
();
let
mut
signature_data
=
auth_data
.clone
();
signature_data
.extend
(
client_data_hash
);
signature_data
.extend
(
client_data_hash
);
let
signature
=
credential
let
signature
=
credential
...
@@ -639,7 +765,7 @@ where
...
@@ -639,7 +765,7 @@ where
String
::
from
(
U2F_VERSION_STRING
),
String
::
from
(
U2F_VERSION_STRING
),
String
::
from
(
FIDO2_VERSION_STRING
),
String
::
from
(
FIDO2_VERSION_STRING
),
],
],
extensions
:
Some
(
vec!
[]),
extensions
:
Some
(
vec!
[
String
::
from
(
"hmac-secret"
)
]),
aaguid
:
*
AAGUID
,
aaguid
:
*
AAGUID
,
options
:
Some
(
options_map
),
options
:
Some
(
options_map
),
max_msg_size
:
Some
(
1024
),
max_msg_size
:
Some
(
1024
),
...
@@ -948,7 +1074,7 @@ where
...
@@ -948,7 +1074,7 @@ where
#[cfg(test)]
#[cfg(test)]
mod
test
{
mod
test
{
use
super
::
data_formats
::{
use
super
::
data_formats
::{
GetAssertionOptions
,
MakeCredentialOptions
,
PublicKeyCredentialRpEntity
,
Extensions
,
GetAssertionOptions
,
MakeCredentialOptions
,
PublicKeyCredentialRpEntity
,
PublicKeyCredentialUserEntity
,
PublicKeyCredentialUserEntity
,
};
};
use
super
::
*
;
use
super
::
*
;
...
@@ -970,13 +1096,15 @@ mod test {
...
@@ -970,13 +1096,15 @@ mod test {
let
mut
expected_response
=
vec!
[
0x00
,
0xA6
,
0x01
];
let
mut
expected_response
=
vec!
[
0x00
,
0xA6
,
0x01
];
// The difference here is a longer array of supported versions.
// The difference here is a longer array of supported versions.
#[cfg(not(feature
=
"with_ctap1"
))]
#[cfg(not(feature
=
"with_ctap1"
))]
expected_response
.extend
(
&
[
expected_response
.extend
(
&
[
0x81
,
0x68
,
0x46
,
0x49
,
0x44
,
0x4F
,
0x5F
,
0x32
,
0x5F
,
0x30
]);
0x81
,
0x68
,
0x46
,
0x49
,
0x44
,
0x4F
,
0x5F
,
0x32
,
0x5F
,
0x30
,
0x02
,
0x80
,
0x03
,
0x50
,
]);
#[cfg(feature
=
"with_ctap1"
)]
#[cfg(feature
=
"with_ctap1"
)]
expected_response
.extend
(
&
[
expected_response
.extend
(
&
[
0x82
,
0x66
,
0x55
,
0x32
,
0x46
,
0x5F
,
0x56
,
0x32
,
0x68
,
0x46
,
0x49
,
0x44
,
0x4F
,
0x5F
,
0x82
,
0x66
,
0x55
,
0x32
,
0x46
,
0x5F
,
0x56
,
0x32
,
0x68
,
0x46
,
0x49
,
0x44
,
0x4F
,
0x5F
,
0x32
,
0x5F
,
0x30
,
0x02
,
0x80
,
0x03
,
0x50
,
0x32
,
0x5F
,
0x30
,
]);
expected_response
.extend
(
&
[
0x02
,
0x81
,
0x6B
,
0x68
,
0x6D
,
0x61
,
0x63
,
0x2D
,
0x73
,
0x65
,
0x63
,
0x72
,
0x65
,
0x74
,
0x03
,
0x50
,
]);
]);
expected_response
.extend
(
AAGUID
);
expected_response
.extend
(
AAGUID
);
expected_response
.extend
(
&
[
expected_response
.extend
(
&
[
...
@@ -1130,6 +1258,7 @@ mod test {
...
@@ -1130,6 +1258,7 @@ mod test {
rp_id
:
String
::
from
(
"example.com"
),
rp_id
:
String
::
from
(
"example.com"
),
user_handle
:
vec!
[],
user_handle
:
vec!
[],
other_ui
:
None
,
other_ui
:
None
,
cred_random
:
None
,
};
};
assert!
(
ctap_state
assert!
(
ctap_state
.persistent_store
.persistent_store
...
@@ -1153,6 +1282,54 @@ mod test {
...
@@ -1153,6 +1282,54 @@ mod test {
);
);
}
}
#[test]
fn
test_process_make_credential_hmac_secret
()
{
let
mut
rng
=
ThreadRng256
{};
let
user_immediately_present
=
|
_
|
Ok
(());
let
mut
ctap_state
=
CtapState
::
new
(
&
mut
rng
,
user_immediately_present
);
let
mut
extension_map
=
BTreeMap
::
new
();
extension_map
.insert
(
"hmac-secret"
.to_string
(),
cbor_bool!
(
true
));
let
extensions
=
Some
(
Extensions
::
new
(
extension_map
));
let
mut
make_credential_params
=
create_minimal_make_credential_parameters
();
make_credential_params
.extensions
=
extensions
;
let
make_credential_response
=
ctap_state
.process_make_credential
(
make_credential_params
,
DUMMY_CHANNEL_ID
);
match
make_credential_response
.unwrap
()
{
ResponseData
::
AuthenticatorMakeCredential
(
make_credential_response
)
=>
{
let
AuthenticatorMakeCredentialResponse
{
fmt
,
auth_data
,
att_stmt
,
}
=
make_credential_response
;
// The expected response is split to only assert the non-random parts.
assert_eq!
(
fmt
,
"packed"
);
let
mut
expected_auth_data
=
vec!
[
0xA3
,
0x79
,
0xA6
,
0xF6
,
0xEE
,
0xAF
,
0xB9
,
0xA5
,
0x5E
,
0x37
,
0x8C
,
0x11
,
0x80
,
0x34
,
0xE2
,
0x75
,
0x1E
,
0x68
,
0x2F
,
0xAB
,
0x9F
,
0x2D
,
0x30
,
0xAB
,
0x13
,
0xD2
,
0x12
,
0x55
,
0x86
,
0xCE
,
0x19
,
0x47
,
0xC1
,
0x00
,
0x00
,
0x00
,
0x00
,
];
expected_auth_data
.extend
(
AAGUID
);
expected_auth_data
.extend
(
&
[
0x00
,
0x20
]);
assert_eq!
(
auth_data
[
0
..
expected_auth_data
.len
()],
expected_auth_data
[
..
]
);
let
expected_extension_cbor
=
vec!
[
0xA1
,
0x6B
,
0x68
,
0x6D
,
0x61
,
0x63
,
0x2D
,
0x73
,
0x65
,
0x63
,
0x72
,
0x65
,
0x74
,
0xF5
,
];
assert_eq!
(
auth_data
[
auth_data
.len
()
-
expected_extension_cbor
.len
()
..
auth_data
.len
()],
expected_extension_cbor
[
..
]
);
assert_eq!
(
att_stmt
.alg
,
SignatureAlgorithm
::
ES256
as
i64
);
}
_
=>
panic!
(
"Invalid response type"
),
}
}
#[test]
#[test]
fn
test_process_make_credential_cancelled
()
{
fn
test_process_make_credential_cancelled
()
{
let
mut
rng
=
ThreadRng256
{};
let
mut
rng
=
ThreadRng256
{};
...
@@ -1216,6 +1393,53 @@ mod test {
...
@@ -1216,6 +1393,53 @@ mod test {
}
}
}
}
#[test]
fn
test_residential_process_get_assertion_hmac_secret
()
{
let
mut
rng
=
ThreadRng256
{};
let
sk
=
crypto
::
ecdh
::
SecKey
::
gensk
(
&
mut
rng
);
let
user_immediately_present
=
|
_
|
Ok
(());
let
mut
ctap_state
=
CtapState
::
new
(
&
mut
rng
,
user_immediately_present
);
let
mut
extension_map
=
BTreeMap
::
new
();
extension_map
.insert
(
"hmac-secret"
.to_string
(),
cbor_bool!
(
true
));
let
make_extensions
=
Some
(
Extensions
::
new
(
extension_map
));
let
mut
make_credential_params
=
create_minimal_make_credential_parameters
();
make_credential_params
.extensions
=
make_extensions
;
assert!
(
ctap_state
.process_make_credential
(
make_credential_params
,
DUMMY_CHANNEL_ID
)
.is_ok
());
let
pk
=
sk
.genpk
();
let
hmac_secret_parameters
=
cbor_map!
{
1
=>
cbor
::
Value
::
Map
(
CoseKey
::
from
(
pk
)
.0
),
2
=>
vec!
[
0
;
32
],
3
=>
vec!
[
0
;
16
],
};
let
mut
extension_map
=
BTreeMap
::
new
();
extension_map
.insert
(
"hmac-secret"
.to_string
(),
hmac_secret_parameters
);
let
get_extensions
=
Some
(
Extensions
::
new
(
extension_map
));
let
get_assertion_params
=
AuthenticatorGetAssertionParameters
{
rp_id
:
String
::
from
(
"example.com"
),
client_data_hash
:
vec!
[
0xCD
],
allow_list
:
None
,
extensions
:
get_extensions
,
options
:
GetAssertionOptions
{
up
:
false
,
uv
:
false
,
},
pin_uv_auth_param
:
None
,
pin_uv_auth_protocol
:
None
,
};
let
get_assertion_response
=
ctap_state
.process_get_assertion
(
get_assertion_params
,
DUMMY_CHANNEL_ID
);
assert_eq!
(
get_assertion_response
,
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
)
);
}
#[test]
#[test]
fn
test_process_reset
()
{
fn
test_process_reset
()
{
let
mut
rng
=
ThreadRng256
{};
let
mut
rng
=
ThreadRng256
{};
...
@@ -1231,6 +1455,7 @@ mod test {
...
@@ -1231,6 +1455,7 @@ mod test {
rp_id
:
String
::
from
(
"example.com"
),
rp_id
:
String
::
from
(
"example.com"
),
user_handle
:
vec!
[],
user_handle
:
vec!
[],
other_ui
:
None
,
other_ui
:
None
,
cred_random
:
None
,
};
};
assert!
(
ctap_state
assert!
(
ctap_state
.persistent_store
.persistent_store
...
@@ -1294,4 +1519,32 @@ mod test {
...
@@ -1294,4 +1519,32 @@ mod test {
.is_none
());
.is_none
());
}
}
}
}
#[test]
fn
test_encrypt_hmac_secret_output
()
{
let
shared_secret
=
[
0x55
;
32
];
let
salt_enc
=
[
0x5E
;
32
];
let
cred_random
=
[
0xC9
;
32
];
let
output
=
encrypt_hmac_secret_output
(
&
shared_secret
,
&
salt_enc
,
&
cred_random
);
assert_eq!
(
output
.unwrap
()
.len
(),
32
);
let
salt_enc
=
[
0x5E
;
48
];
let
output
=
encrypt_hmac_secret_output
(
&
shared_secret
,
&
salt_enc
,
&
cred_random
);
assert_eq!
(
output
,
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
)
);
let
salt_enc
=
[
0x5E
;
64
];
let
output
=
encrypt_hmac_secret_output
(
&
shared_secret
,
&
salt_enc
,
&
cred_random
);
assert_eq!
(
output
.unwrap
()
.len
(),
64
);
let
salt_enc
=
[
0x5E
;
32
];
let
cred_random
=
[
0xC9
;
33
];
let
output
=
encrypt_hmac_secret_output
(
&
shared_secret
,
&
salt_enc
,
&
cred_random
);
assert_eq!
(
output
,
Err
(
Ctap2StatusCode
::
CTAP2_ERR_UNSUPPORTED_EXTENSION
)
);
}
}
}
This diff is collapsed.
Click to expand it.
src/ctap/storage.rs
+
2
−
0
View file @
dabbe386
...
@@ -445,6 +445,7 @@ mod test {
...
@@ -445,6 +445,7 @@ mod test {
rp_id
:
String
::
from
(
rp_id
),
rp_id
:
String
::
from
(
rp_id
),
user_handle
,
user_handle
,
other_ui
:
None
,
other_ui
:
None
,
cred_random
:
None
,
}
}
}
}
...
@@ -613,6 +614,7 @@ mod test {
...
@@ -613,6 +614,7 @@ mod test {
rp_id
:
String
::
from
(
"example.com"
),
rp_id
:
String
::
from
(
"example.com"
),
user_handle
:
vec!
[
0x00
],
user_handle
:
vec!
[
0x00
],
other_ui
:
None
,
other_ui
:
None
,
cred_random
:
None
,
};
};
assert_eq!
(
found_credential
,
Some
(
expected_credential
));
assert_eq!
(
found_credential
,
Some
(
expected_credential
));
}
}
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment