diff --git a/.github/workflows/cargo_check.yml b/.github/workflows/cargo_check.yml
index c54fc9e34ab5309e19d28dc4f9894bbcba72637f..07c1c0a8d2420b20be74f8238c7aa362af918bad 100644
--- a/.github/workflows/cargo_check.yml
+++ b/.github/workflows/cargo_check.yml
@@ -40,6 +40,12 @@ jobs:
           command: check
           args: --target thumbv7em-none-eabi --release --features with_ctap1
 
+      - name: Check OpenSK with_ctap2_1
+        uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: --target thumbv7em-none-eabi --release --features with_ctap2_1
+
       - name: Check OpenSK debug_ctap
         uses: actions-rs/cargo@v1
         with:
@@ -76,11 +82,17 @@ jobs:
           command: check
           args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1
 
-      - name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
+      - name: Check OpenSK debug_ctap,with_ctap2_1
+        uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap2_1
+
+      - name: Check OpenSK debug_ctap,with_ctap1,with_ctap2_1,panic_console,debug_allocations,verbose
         uses: actions-rs/cargo@v1
         with:
           command: check
-          args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
+          args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,with_ctap2_1,panic_console,debug_allocations,verbose
 
       - name: Check examples
         uses: actions-rs/cargo@v1
diff --git a/.github/workflows/opensk_test.yml b/.github/workflows/opensk_test.yml
index 6b177638145cc60ab477b5a80bb044a64d9ae806..ece41e71b9667f0220a01423d6f6e375e41fa320 100644
--- a/.github/workflows/opensk_test.yml
+++ b/.github/workflows/opensk_test.yml
@@ -49,3 +49,27 @@ jobs:
           command: test
           args: --features std,with_ctap1
 
+      - name: Unit testing of CTAP2 (release mode + CTAP2.1)
+        uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --release --features std,with_ctap2_1
+
+      - name: Unit testing of CTAP2 (debug mode + CTAP2.1)
+        uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --features std,with_ctap2_1
+
+      - name: Unit testing of CTAP2 (release mode + CTAP1 + CTAP2.1)
+        uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --release --features std,with_ctap1,with_ctap2_1
+
+      - name: Unit testing of CTAP2 (debug mode + CTAP1 + CTAP2.1)
+        uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --features std,with_ctap1,with_ctap2_1
+
diff --git a/Cargo.toml b/Cargo.toml
index 1d1ad6e18f6a1651f9c5a59a5d4d4f70640e6c5c..4c2d16cb96fa27c93f718b87675a5e99dcdd3c29 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
 ram_storage = []
 verbose = ["debug_ctap"]
 with_ctap1 = ["crypto/with_ctap1"]
+with_ctap2_1 = []
 
 [dev-dependencies]
 elf2tab = "0.4.0"
diff --git a/README.md b/README.md
index 3f71d717201e0bfde0b4de2060305951cd2f0587..28093fc3a83c99ca214bfad7891803a21c2ded35 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,10 @@ Although we tested and implemented our firmware based on the published
 [CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html),
 our implementation was not reviewed nor officially tested and doesn't claim to
 be FIDO Certified.
+We started adding features of the upcoming next version of the
+[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html).
+The development is currently between 2.0 and 2.1, with updates hidden behind a feature flag.
+Please add the flag `--ctap2.1` to the deploy command to include them.
 
 ### Cryptography
 
diff --git a/deploy.py b/deploy.py
index 5fb65f77d2dc21454713f06a5b719bc9243ca699..b7d1199ad4a3799b7e862b1307272f9335304165 100755
--- a/deploy.py
+++ b/deploy.py
@@ -786,6 +786,14 @@ if __name__ == "__main__":
       help=("Compiles the OpenSK application without backward compatible "
             "support for U2F/CTAP1 protocol."),
   )
+  main_parser.add_argument(
+      "--ctap2.1",
+      action=RemoveConstAction,
+      const="with_ctap2_1",
+      dest="features",
+      help=("Compiles the OpenSK application with backward compatible "
+            "support for CTAP2.1 protocol."),
+  )
   main_parser.add_argument(
       "--regen-keys",
       action="store_true",
diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh
index 36bde36da10d929257197db4316cc2aa11f96b2b..07b652e2756f8e8142c5c94b0dc4cc770dd2dcc6 100755
--- a/run_desktop_tests.sh
+++ b/run_desktop_tests.sh
@@ -30,6 +30,7 @@ cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
 echo "Checking that CTAP2 builds properly..."
 cargo check --release --target=thumbv7em-none-eabi
 cargo check --release --target=thumbv7em-none-eabi --features with_ctap1
+cargo check --release --target=thumbv7em-none-eabi --features with_ctap2_1
 cargo check --release --target=thumbv7em-none-eabi --features debug_ctap
 cargo check --release --target=thumbv7em-none-eabi --features panic_console
 cargo check --release --target=thumbv7em-none-eabi --features debug_allocations
@@ -86,4 +87,16 @@ then
 
   echo "Running unit tests on the desktop (debug mode + CTAP1)..."
   cargo test --features std,with_ctap1
+
+  echo "Running unit tests on the desktop (release mode + CTAP2.1)..."
+  cargo test --release --features std,with_ctap2_1
+
+  echo "Running unit tests on the desktop (debug mode + CTAP2.1)..."
+  cargo test --features std,with_ctap2_1
+
+  echo "Running unit tests on the desktop (release mode + CTAP1 + CTAP2.1)..."
+  cargo test --release --features std,with_ctap1,with_ctap2_1
+
+  echo "Running unit tests on the desktop (debug mode + CTAP1 + CTAP2.1)..."
+  cargo test --features std,with_ctap1,with_ctap2_1
 fi
diff --git a/src/ctap/command.rs b/src/ctap/command.rs
index 129c671b4c86a5a8d82db198061d89625e7b2dbc..19132bc9d1363aea7ff2839da3a9f86b6726135e 100644
--- a/src/ctap/command.rs
+++ b/src/ctap/command.rs
@@ -13,16 +13,21 @@
 // limitations under the License.
 
 use super::data_formats::{
-    ok_or_missing, read_array, read_byte_string, read_integer, read_map, read_text_string,
-    read_unsigned, ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions,
-    MakeCredentialOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialRpEntity,
-    PublicKeyCredentialType, PublicKeyCredentialUserEntity,
+    ok_or_missing, read_array, read_byte_string, read_map, read_text_string, read_unsigned,
+    ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions, MakeCredentialOptions,
+    PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
+    PublicKeyCredentialUserEntity,
 };
 use super::status_code::Ctap2StatusCode;
 use alloc::string::String;
 use alloc::vec::Vec;
 use core::convert::TryFrom;
 
+// Depending on your memory, you can use Some(n) to limit request sizes in
+// MakeCredential and GetAssertion. This affects allowList and excludeList.
+// You might also want to set the max credential size in process_get_info then.
+pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
+
 // CTAP specification (version 20190130) section 6.1
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
 pub enum Command {
@@ -106,7 +111,7 @@ pub struct AuthenticatorMakeCredentialParameters {
     pub client_data_hash: Vec<u8>,
     pub rp: PublicKeyCredentialRpEntity,
     pub user: PublicKeyCredentialUserEntity,
-    pub pub_key_cred_params: Vec<(PublicKeyCredentialType, i64)>,
+    pub pub_key_cred_params: Vec<PublicKeyCredentialParameter>,
     pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>,
     pub extensions: Option<Extensions>,
     // Even though options are optional, we can use the default if not present.
@@ -132,23 +137,19 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
         )?)?;
 
         let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?;
-        let mut pub_key_cred_params = vec![];
-        for cred_param_map_value in cred_param_vec {
-            let cred_param_map = read_map(cred_param_map_value)?;
-            let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
-                cred_param_map.get(&cbor_text!("type")),
-            )?)?;
-            let alg = read_integer(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
-            pub_key_cred_params.push((cred_type, alg));
-        }
+        let pub_key_cred_params = cred_param_vec
+            .iter()
+            .map(PublicKeyCredentialParameter::try_from)
+            .collect::<Result<Vec<PublicKeyCredentialParameter>, Ctap2StatusCode>>()?;
 
         let exclude_list = match param_map.get(&cbor_unsigned!(5)) {
             Some(entry) => {
                 let exclude_list_vec = read_array(entry)?;
-                let mut exclude_list = vec![];
-                for exclude_list_value in exclude_list_vec {
-                    exclude_list.push(PublicKeyCredentialDescriptor::try_from(exclude_list_value)?);
-                }
+                let exclude_list = exclude_list_vec
+                    .iter()
+                    .take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len()))
+                    .map(PublicKeyCredentialDescriptor::try_from)
+                    .collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
                 Some(exclude_list)
             }
             None => None,
@@ -216,10 +217,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
         let allow_list = match param_map.get(&cbor_unsigned!(3)) {
             Some(entry) => {
                 let allow_list_vec = read_array(entry)?;
-                let mut allow_list = vec![];
-                for allow_list_value in allow_list_vec {
-                    allow_list.push(PublicKeyCredentialDescriptor::try_from(allow_list_value)?);
-                }
+                let allow_list = allow_list_vec
+                    .iter()
+                    .take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len()))
+                    .map(PublicKeyCredentialDescriptor::try_from)
+                    .collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
                 Some(allow_list)
             }
             None => None,
@@ -316,8 +318,10 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
 #[cfg(test)]
 mod test {
     use super::super::data_formats::{
-        AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
+        AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
+        PublicKeyCredentialUserEntity,
     };
+    use super::super::ES256_CRED_PARAM;
     use super::*;
     use alloc::collections::BTreeMap;
 
@@ -336,10 +340,7 @@ mod test {
                 "displayName" => "bar",
                 "icon" => "example.com/foo/icon.png",
             },
-            4 => cbor_array![ cbor_map! {
-                "type" => "public-key",
-                "alg" => -7
-            } ],
+            4 => cbor_array![ES256_CRED_PARAM],
             5 => cbor_array![],
             8 => vec![0x12, 0x34],
             9 => 1,
@@ -362,7 +363,6 @@ mod test {
             user_display_name: Some("bar".to_string()),
             user_icon: Some("example.com/foo/icon.png".to_string()),
         };
-        let pub_key_cred_param = (PublicKeyCredentialType::PublicKey, -7);
         let options = MakeCredentialOptions {
             rk: false,
             uv: false,
@@ -371,7 +371,7 @@ mod test {
             client_data_hash,
             rp,
             user,
-            pub_key_cred_params: vec![pub_key_cred_param],
+            pub_key_cred_params: vec![ES256_CRED_PARAM],
             exclude_list: Some(vec![]),
             extensions: None,
             options,
diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs
index 3a2bed91e85089124c5780dd399b39bbd42902d4..8e6c7d9c365a149d8c1b4a82e1ad019448a4ecff 100644
--- a/src/ctap/data_formats.rs
+++ b/src/ctap/data_formats.rs
@@ -19,6 +19,7 @@ use alloc::vec::Vec;
 use core::convert::TryFrom;
 use crypto::{ecdh, ecdsa};
 
+// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
 pub struct PublicKeyCredentialRpEntity {
     pub rp_id: String,
@@ -48,6 +49,7 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialRpEntity {
     }
 }
 
+// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
 pub struct PublicKeyCredentialUserEntity {
     pub user_id: Vec<u8>,
@@ -94,16 +96,22 @@ impl From<PublicKeyCredentialUserEntity> for cbor::Value {
     }
 }
 
+// https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype
 #[derive(Clone, PartialEq)]
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
 pub enum PublicKeyCredentialType {
     PublicKey,
+    // This is the default for all strings not covered above.
+    // Unknown types should be ignored, instead of returning errors.
+    Unknown,
 }
 
 impl From<PublicKeyCredentialType> for cbor::Value {
     fn from(cred_type: PublicKeyCredentialType) -> Self {
         match cred_type {
             PublicKeyCredentialType::PublicKey => "public-key",
+            // We should never create this credential type.
+            PublicKeyCredentialType::Unknown => "unknown",
         }
         .into()
     }
@@ -116,11 +124,43 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialType {
         let cred_type_string = read_text_string(cbor_value)?;
         match &cred_type_string[..] {
             "public-key" => Ok(PublicKeyCredentialType::PublicKey),
-            _ => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
+            _ => Ok(PublicKeyCredentialType::Unknown),
         }
     }
 }
 
+// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
+#[derive(PartialEq)]
+#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
+pub struct PublicKeyCredentialParameter {
+    pub cred_type: PublicKeyCredentialType,
+    pub alg: SignatureAlgorithm,
+}
+
+impl TryFrom<&cbor::Value> for PublicKeyCredentialParameter {
+    type Error = Ctap2StatusCode;
+
+    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
+        let cred_param_map = read_map(cbor_value)?;
+        let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
+            cred_param_map.get(&cbor_text!("type")),
+        )?)?;
+        let alg =
+            SignatureAlgorithm::try_from(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
+        Ok(Self { cred_type, alg })
+    }
+}
+
+impl From<PublicKeyCredentialParameter> for cbor::Value {
+    fn from(cred_param: PublicKeyCredentialParameter) -> Self {
+        cbor_map_options! {
+            "type" => cred_param.cred_type,
+            "alg" => cred_param.alg as i64,
+        }
+    }
+}
+
+// https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
 pub enum AuthenticatorTransport {
     Usb,
@@ -156,6 +196,7 @@ impl TryFrom<&cbor::Value> for AuthenticatorTransport {
     }
 }
 
+// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
 pub struct PublicKeyCredentialDescriptor {
     pub key_type: PublicKeyCredentialType,
@@ -175,10 +216,11 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialDescriptor {
         let transports = match cred_desc_map.get(&cbor_text!("transports")) {
             Some(exclude_entry) => {
                 let transport_vec = read_array(exclude_entry)?;
-                let mut transports = vec![];
-                for transport_value in transport_vec {
-                    transports.push(AuthenticatorTransport::try_from(transport_value)?);
-                }
+                let transports = transport_vec
+                    .iter()
+                    .map(AuthenticatorTransport::try_from)
+                    .collect::<Result<Vec<AuthenticatorTransport>, Ctap2StatusCode>>(
+                )?;
                 Some(transports)
             }
             None => None,
@@ -349,6 +391,7 @@ impl TryFrom<&cbor::Value> for GetAssertionOptions {
     }
 }
 
+// https://www.w3.org/TR/webauthn/#packed-attestation
 #[cfg_attr(test, derive(PartialEq))]
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
 pub struct PackedAttestationStatement {
@@ -369,12 +412,27 @@ impl From<PackedAttestationStatement> for cbor::Value {
     }
 }
 
-#[cfg_attr(test, derive(PartialEq))]
+#[derive(PartialEq)]
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
 pub enum SignatureAlgorithm {
     ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize,
+    // This is the default for all numbers not covered above.
+    // Unknown types should be ignored, instead of returning errors.
+    Unknown = 0,
 }
 
+impl TryFrom<&cbor::Value> for SignatureAlgorithm {
+    type Error = Ctap2StatusCode;
+
+    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
+        match read_integer(cbor_value)? {
+            ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256),
+            _ => Ok(SignatureAlgorithm::Unknown),
+        }
+    }
+}
+
+// https://www.w3.org/TR/webauthn/#public-key-credential-source
 #[derive(Clone)]
 #[cfg_attr(test, derive(PartialEq))]
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
@@ -638,6 +696,7 @@ mod test {
     use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
     use super::*;
     use alloc::collections::BTreeMap;
+    use crypto::rng256::{Rng256, ThreadRng256};
 
     #[test]
     fn test_read_unsigned() {
@@ -949,6 +1008,26 @@ mod test {
         assert_eq!(credential_type, Ok(expected_credential_type));
         let created_cbor: cbor::Value = credential_type.unwrap().into();
         assert_eq!(created_cbor, cbor_credential_type);
+
+        let cbor_unknown_type = cbor_text!("unknown-type");
+        let unknown_type = PublicKeyCredentialType::try_from(&cbor_unknown_type);
+        let expected_unknown_type = PublicKeyCredentialType::Unknown;
+        assert_eq!(unknown_type, Ok(expected_unknown_type));
+    }
+
+    #[test]
+    fn test_from_into_signature_algorithm() {
+        let cbor_signature_algorithm = cbor_int!(ecdsa::PubKey::ES256_ALGORITHM);
+        let signature_algorithm = SignatureAlgorithm::try_from(&cbor_signature_algorithm);
+        let expected_signature_algorithm = SignatureAlgorithm::ES256;
+        assert_eq!(signature_algorithm, Ok(expected_signature_algorithm));
+        let created_cbor: cbor::Value = cbor_int!(signature_algorithm.unwrap() as i64);
+        assert_eq!(created_cbor, cbor_signature_algorithm);
+
+        let cbor_unknown_algorithm = cbor_int!(-1);
+        let unknown_algorithm = SignatureAlgorithm::try_from(&cbor_unknown_algorithm);
+        let expected_unknown_algorithm = SignatureAlgorithm::Unknown;
+        assert_eq!(unknown_algorithm, Ok(expected_unknown_algorithm));
     }
 
     #[test]
@@ -965,6 +1044,23 @@ mod test {
         assert_eq!(created_cbor, cbor_authenticator_transport);
     }
 
+    #[test]
+    fn test_from_into_public_key_credential_parameter() {
+        let cbor_credential_parameter = cbor_map! {
+            "type" => "public-key",
+            "alg" => ecdsa::PubKey::ES256_ALGORITHM,
+        };
+        let credential_parameter =
+            PublicKeyCredentialParameter::try_from(&cbor_credential_parameter);
+        let expected_credential_parameter = PublicKeyCredentialParameter {
+            cred_type: PublicKeyCredentialType::PublicKey,
+            alg: SignatureAlgorithm::ES256,
+        };
+        assert_eq!(credential_parameter, Ok(expected_credential_parameter));
+        let created_cbor: cbor::Value = credential_parameter.unwrap().into();
+        assert_eq!(created_cbor, cbor_credential_parameter);
+    }
+
     #[test]
     fn test_from_into_public_key_credential_descriptor() {
         let cbor_credential_descriptor = cbor_map! {
@@ -985,7 +1081,7 @@ mod test {
     }
 
     #[test]
-    fn test_from_extensions() {
+    fn test_from_into_extensions() {
         let cbor_extensions = cbor_map! {
             "the_answer" => 42,
         };
@@ -995,6 +1091,53 @@ mod test {
             .0
             .insert("the_answer".to_string(), cbor_int!(42));
         assert_eq!(extensions, Ok(expected_extensions));
+        let created_cbor: cbor::Value = extensions.unwrap().into();
+        assert_eq!(created_cbor, cbor_extensions);
+    }
+
+    #[test]
+    fn test_from_into_get_assertion_hmac_secret_output() {
+        let cbor_output = cbor_bytes![vec![0xC0; 32]];
+        let output = GetAssertionHmacSecretOutput::try_from(&cbor_output);
+        let expected_output = GetAssertionHmacSecretOutput(vec![0xC0; 32]);
+        assert_eq!(output, Ok(expected_output));
+        let created_cbor: cbor::Value = output.unwrap().into();
+        assert_eq!(created_cbor, cbor_output);
+    }
+
+    #[test]
+    fn test_hmac_secret_extension() {
+        let cbor_extensions = cbor_map! {
+            "hmac-secret" => true,
+        };
+        let extensions = Extensions::try_from(&cbor_extensions).unwrap();
+        assert!(extensions.has_make_credential_hmac_secret().unwrap());
+
+        let cbor_extensions = cbor_map! {
+            "hmac-secret" => false,
+        };
+        let extensions = Extensions::try_from(&cbor_extensions).unwrap();
+        assert!(!extensions.has_make_credential_hmac_secret().unwrap());
+
+        let mut rng = ThreadRng256 {};
+        let sk = crypto::ecdh::SecKey::gensk(&mut rng);
+        let pk = sk.genpk();
+        let cose_key = CoseKey::from(pk.clone());
+        let cbor_extensions = cbor_map! {
+            "hmac-secret" => cbor_map! {
+                1 => cbor::Value::Map(cose_key.0.clone()),
+                2 => vec![0x02; 32],
+                3 => vec![0x03; 32],
+            },
+        };
+        let extensions = Extensions::try_from(&cbor_extensions).unwrap();
+        let get_assertion_input = extensions.get_assertion_hmac_secret();
+        let expected_input = GetAssertionHmacSecretInput {
+            key_agreement: cose_key,
+            salt_enc: vec![0x02; 32],
+            salt_auth: vec![0x03; 32],
+        };
+        assert_eq!(get_assertion_input, Some(Ok(expected_input)));
     }
 
     #[test]
@@ -1046,8 +1189,6 @@ mod test {
 
     #[test]
     fn test_from_into_cose_key() {
-        use crypto::rng256::ThreadRng256;
-
         let mut rng = ThreadRng256 {};
         let sk = crypto::ecdh::SecKey::gensk(&mut rng);
         let pk = sk.genpk();
@@ -1068,8 +1209,6 @@ mod test {
 
     #[test]
     fn test_credential_source_cbor_round_trip() {
-        use crypto::rng256::{Rng256, ThreadRng256};
-
         let mut rng = ThreadRng256 {};
         let credential = PublicKeyCredentialSource {
             key_type: PublicKeyCredentialType::PublicKey,
diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs
index e4a11516265f2ed23771179c726284dfba5d4628..a39a983f41524b2728f220b0cf4ed34da2b9b46c 100644
--- a/src/ctap/mod.rs
+++ b/src/ctap/mod.rs
@@ -23,14 +23,18 @@ pub mod status_code;
 mod storage;
 mod timed_permission;
 
+#[cfg(feature = "with_ctap2_1")]
+use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
 use self::command::{
     AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
     AuthenticatorMakeCredentialParameters, Command,
 };
+#[cfg(feature = "with_ctap2_1")]
+use self::data_formats::AuthenticatorTransport;
 use self::data_formats::{
     ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
-    PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType,
-    PublicKeyCredentialUserEntity, SignatureAlgorithm,
+    PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
+    PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
 };
 use self::hid::ChannelID;
 use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
@@ -99,6 +103,13 @@ pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0";
 #[cfg(feature = "with_ctap1")]
 pub const U2F_VERSION_STRING: &str = "U2F_V2";
 
+// We currently only support one algorithm for signatures: ES256.
+// This algorithm is requested in MakeCredential and advertized in GetInfo.
+pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialParameter {
+    cred_type: PublicKeyCredentialType::PublicKey,
+    alg: SignatureAlgorithm::ES256,
+};
+
 fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
     if pin_auth.len() != PIN_AUTH_LENGTH {
         return false;
@@ -413,15 +424,7 @@ where
             }
         }
 
-        let has_es_256 = pub_key_cred_params
-            .iter()
-            .any(|(credential_type, algorithm)| {
-                // Even though there is only one type now, checking seems safer in
-                // case of extension so you can't forget to update here.
-                *credential_type == PublicKeyCredentialType::PublicKey
-                    && *algorithm == SignatureAlgorithm::ES256 as i64
-            });
-        if !has_es_256 {
+        if !pub_key_cred_params.contains(&ES256_CRED_PARAM) {
             return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
         }
 
@@ -751,7 +754,7 @@ where
 
     fn process_get_info(&self) -> Result<ResponseData, Ctap2StatusCode> {
         let mut options_map = BTreeMap::new();
-        // TODO(kaczmarczyck) add FIDO 2.1 options
+        // TODO(kaczmarczyck) add authenticatorConfig and credProtect options
         options_map.insert(String::from("rk"), true);
         options_map.insert(String::from("up"), true);
         options_map.insert(
@@ -772,6 +775,18 @@ where
                 pin_protocols: Some(vec![
                     CtapState::<R, CheckUserPresence>::PIN_PROTOCOL_VERSION,
                 ]),
+                #[cfg(feature = "with_ctap2_1")]
+                max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
+                // You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your
+                // browser passes that value, it might be used to fingerprint.
+                #[cfg(feature = "with_ctap2_1")]
+                max_credential_id_length: None,
+                #[cfg(feature = "with_ctap2_1")]
+                transports: Some(vec![AuthenticatorTransport::Usb]),
+                #[cfg(feature = "with_ctap2_1")]
+                algorithms: Some(vec![ES256_CRED_PARAM]),
+                #[cfg(feature = "with_ctap2_1")]
+                firmware_version: None,
             },
         ))
     }
@@ -1093,6 +1108,9 @@ mod test {
         let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
         let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID);
 
+        #[cfg(feature = "with_ctap2_1")]
+        let mut expected_response = vec![0x00, 0xA8, 0x01];
+        #[cfg(not(feature = "with_ctap2_1"))]
         let mut expected_response = vec![0x00, 0xA6, 0x01];
         // The difference here is a longer array of supported versions.
         #[cfg(not(feature = "with_ctap1"))]
@@ -1111,6 +1129,15 @@ mod test {
             0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
             0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
         ]);
+        #[cfg(feature = "with_ctap2_1")]
+        expected_response.extend(
+            [
+                0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C, 0x67, 0x26,
+                0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B,
+                0x65, 0x79,
+            ]
+            .iter(),
+        );
 
         assert_eq!(info_reponse, expected_response);
     }
@@ -1128,10 +1155,7 @@ mod test {
             user_display_name: None,
             user_icon: None,
         };
-        let pub_key_cred_params = vec![(
-            PublicKeyCredentialType::PublicKey,
-            SignatureAlgorithm::ES256 as i64,
-        )];
+        let pub_key_cred_params = vec![ES256_CRED_PARAM];
         let options = MakeCredentialOptions {
             rk: true,
             uv: false,
@@ -1228,12 +1252,8 @@ mod test {
         let user_immediately_present = |_| Ok(());
         let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
 
-        let pub_key_cred_params = vec![(
-            PublicKeyCredentialType::PublicKey,
-            SignatureAlgorithm::ES256 as i64 + 1, // any different number works
-        )];
         let mut make_credential_params = create_minimal_make_credential_parameters();
-        make_credential_params.pub_key_cred_params = pub_key_cred_params;
+        make_credential_params.pub_key_cred_params = vec![];
         let make_credential_response =
             ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
 
diff --git a/src/ctap/response.rs b/src/ctap/response.rs
index 4b8a089d93d455bed6601fc902fa581b61f6208f..389b82d9c37d8ba560159fde4e12fb6adb8eef1b 100644
--- a/src/ctap/response.rs
+++ b/src/ctap/response.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#[cfg(feature = "with_ctap2_1")]
+use super::data_formats::{AuthenticatorTransport, PublicKeyCredentialParameter};
 use super::data_formats::{
     CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
     PublicKeyCredentialUserEntity,
@@ -102,16 +104,27 @@ impl From<AuthenticatorGetAssertionResponse> for cbor::Value {
 #[cfg_attr(test, derive(PartialEq))]
 #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
 pub struct AuthenticatorGetInfoResponse {
-    // TODO(kaczmarczyck) add fields from 2.1
+    // TODO(kaczmarczyck) add maxAuthenticatorConfigLength and defaultCredProtect
     pub versions: Vec<String>,
     pub extensions: Option<Vec<String>>,
     pub aaguid: [u8; 16],
     pub options: Option<BTreeMap<String, bool>>,
     pub max_msg_size: Option<u64>,
     pub pin_protocols: Option<Vec<u64>>,
+    #[cfg(feature = "with_ctap2_1")]
+    pub max_credential_count_in_list: Option<u64>,
+    #[cfg(feature = "with_ctap2_1")]
+    pub max_credential_id_length: Option<u64>,
+    #[cfg(feature = "with_ctap2_1")]
+    pub transports: Option<Vec<AuthenticatorTransport>>,
+    #[cfg(feature = "with_ctap2_1")]
+    pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
+    #[cfg(feature = "with_ctap2_1")]
+    pub firmware_version: Option<u64>,
 }
 
 impl From<AuthenticatorGetInfoResponse> for cbor::Value {
+    #[cfg(feature = "with_ctap2_1")]
     fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self {
         let AuthenticatorGetInfoResponse {
             versions,
@@ -120,6 +133,11 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
             options,
             max_msg_size,
             pin_protocols,
+            max_credential_count_in_list,
+            max_credential_id_length,
+            transports,
+            algorithms,
+            firmware_version,
         } = get_info_response;
 
         let options_cbor: Option<cbor::Value> = options.map(|options| {
@@ -131,12 +149,46 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
         });
 
         cbor_map_options! {
-            1 => cbor_array_vec!(versions),
-            2 => extensions.map(|vec| cbor_array_vec!(vec)),
-            3 => &aaguid,
-            4 => options_cbor,
-            5 => max_msg_size,
-            6 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
+            0x01 => cbor_array_vec!(versions),
+            0x02 => extensions.map(|vec| cbor_array_vec!(vec)),
+            0x03 => &aaguid,
+            0x04 => options_cbor,
+            0x05 => max_msg_size,
+            0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
+            0x07 => max_credential_count_in_list,
+            0x08 => max_credential_id_length,
+            0x09 => transports.map(|vec| cbor_array_vec!(vec)),
+            0x0A => algorithms.map(|vec| cbor_array_vec!(vec)),
+            0x0E => firmware_version,
+        }
+    }
+
+    #[cfg(not(feature = "with_ctap2_1"))]
+    fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self {
+        let AuthenticatorGetInfoResponse {
+            versions,
+            extensions,
+            aaguid,
+            options,
+            max_msg_size,
+            pin_protocols,
+        } = get_info_response;
+
+        let options_cbor: Option<cbor::Value> = options.map(|options| {
+            let option_map: BTreeMap<_, _> = options
+                .into_iter()
+                .map(|(key, value)| (cbor_text!(key), cbor_bool!(value)))
+                .collect();
+            cbor_map_btree!(option_map)
+        });
+
+        cbor_map_options! {
+            0x01 => cbor_array_vec!(versions),
+            0x02 => extensions.map(|vec| cbor_array_vec!(vec)),
+            0x03 => &aaguid,
+            0x04 => options_cbor,
+            0x05 => max_msg_size,
+            0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
         }
     }
 }
@@ -168,6 +220,8 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
 #[cfg(test)]
 mod test {
     use super::super::data_formats::PackedAttestationStatement;
+    #[cfg(feature = "with_ctap2_1")]
+    use super::super::ES256_CRED_PARAM;
     use super::*;
 
     #[test]
@@ -228,12 +282,58 @@ mod test {
             options: None,
             max_msg_size: None,
             pin_protocols: None,
+            #[cfg(feature = "with_ctap2_1")]
+            max_credential_count_in_list: None,
+            #[cfg(feature = "with_ctap2_1")]
+            max_credential_id_length: None,
+            #[cfg(feature = "with_ctap2_1")]
+            transports: None,
+            #[cfg(feature = "with_ctap2_1")]
+            algorithms: None,
+            #[cfg(feature = "with_ctap2_1")]
+            firmware_version: None,
+        };
+        let response_cbor: Option<cbor::Value> =
+            ResponseData::AuthenticatorGetInfo(get_info_response).into();
+        let expected_cbor = cbor_map_options! {
+            0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
+            0x03 => vec![0x00; 16],
+        };
+        assert_eq!(response_cbor, Some(expected_cbor));
+    }
+
+    #[test]
+    #[cfg(feature = "with_ctap2_1")]
+    fn test_get_info_optionals_into_cbor() {
+        let mut options_map = BTreeMap::new();
+        options_map.insert(String::from("rk"), true);
+        let get_info_response = AuthenticatorGetInfoResponse {
+            versions: vec!["FIDO_2_0".to_string()],
+            extensions: Some(vec!["extension".to_string()]),
+            aaguid: [0x00; 16],
+            options: Some(options_map),
+            max_msg_size: Some(1024),
+            pin_protocols: Some(vec![1]),
+            max_credential_count_in_list: Some(20),
+            max_credential_id_length: Some(256),
+            transports: Some(vec![AuthenticatorTransport::Usb]),
+            algorithms: Some(vec![ES256_CRED_PARAM]),
+            firmware_version: Some(0),
         };
         let response_cbor: Option<cbor::Value> =
             ResponseData::AuthenticatorGetInfo(get_info_response).into();
         let expected_cbor = cbor_map_options! {
-            1 => cbor_array_vec![vec!["FIDO_2_0"]],
-            3 => vec![0x00; 16],
+            0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
+            0x02 => cbor_array_vec![vec!["extension"]],
+            0x03 => vec![0x00; 16],
+            0x04 => cbor_map! {"rk" => true},
+            0x05 => 1024,
+            0x06 => cbor_array_vec![vec![1]],
+            0x07 => 20,
+            0x08 => 256,
+            0x09 => cbor_array_vec![vec!["usb"]],
+            0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
+            0x0E => 0,
         };
         assert_eq!(response_cbor, Some(expected_cbor));
     }