File size: 8,902 Bytes
08c964e | 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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | import json
from json.decoder import JSONDecodeError
from typing import Union, Optional, List
from .structs import (
PublicKeyCredentialCreationOptions,
PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
AttestationConveyancePreference,
AuthenticatorSelectionCriteria,
AuthenticatorAttachment,
ResidentKeyRequirement,
UserVerificationRequirement,
PublicKeyCredentialParameters,
PublicKeyCredentialDescriptor,
AuthenticatorTransport,
)
from .cose import COSEAlgorithmIdentifier
from .exceptions import InvalidJSONStructure, InvalidRegistrationOptions
from .base64url_to_bytes import base64url_to_bytes
def parse_registration_options_json(
json_val: Union[str, dict]
) -> PublicKeyCredentialCreationOptions:
"""
Parse a JSON form of registration options, as either stringified JSON or a plain dict, into an
instance of `PublicKeyCredentialCreationOptions`. Typically useful in mapping output from
`generate_registration_options()`, that's been persisted as JSON via Redis/etc... back into
structured data.
"""
if isinstance(json_val, str):
try:
json_val = json.loads(json_val)
except JSONDecodeError:
raise InvalidJSONStructure("Unable to decode options as JSON")
if not isinstance(json_val, dict):
raise InvalidJSONStructure("Options were not a JSON object")
"""
Check rp
"""
options_rp = json_val.get("rp")
if not isinstance(options_rp, dict):
raise InvalidJSONStructure("Options missing required rp")
options_rp_id = options_rp.get("id")
if options_rp_id is not None and not isinstance(options_rp_id, str):
raise InvalidJSONStructure("Options rp.id present but not string")
options_rp_name = options_rp.get("name")
if not isinstance(options_rp_name, str):
raise InvalidJSONStructure("Options rp missing required name")
"""
Check user
"""
options_user = json_val.get("user")
if not isinstance(options_user, dict):
raise InvalidJSONStructure("Options missing required user")
options_user_id = options_user.get("id")
if not isinstance(options_user_id, str):
raise InvalidJSONStructure("Options user missing required id")
options_user_name = options_user.get("name")
if not isinstance(options_user_name, str):
raise InvalidJSONStructure("Options user missing required name")
options_user_display_name = options_user.get("displayName")
if not isinstance(options_user_display_name, str):
raise InvalidJSONStructure("Options user missing required displayName")
"""
Check attestation
"""
options_attestation = json_val.get("attestation")
if not isinstance(options_attestation, str):
raise InvalidJSONStructure("Options missing required attestation")
try:
mapped_attestation = AttestationConveyancePreference(options_attestation)
except ValueError as exc:
raise InvalidJSONStructure("Options attestation was invalid value") from exc
"""
Check authenticatorSelection
"""
options_authr_selection = json_val.get("authenticatorSelection")
mapped_authenticator_selection: Optional[AuthenticatorSelectionCriteria] = None
if isinstance(options_authr_selection, dict):
options_authr_selection_attachment = options_authr_selection.get("authenticatorAttachment")
mapped_attachment = None
if options_authr_selection_attachment is not None:
try:
mapped_attachment = AuthenticatorAttachment(options_authr_selection_attachment)
except ValueError as exc:
raise InvalidJSONStructure(
"Options authenticatorSelection attachment was invalid value"
) from exc
options_authr_selection_rkey = options_authr_selection.get("residentKey")
mapped_rkey = None
if options_authr_selection_rkey is not None:
try:
mapped_rkey = ResidentKeyRequirement(options_authr_selection_rkey)
except ValueError as exc:
raise InvalidJSONStructure(
"Options authenticatorSelection residentKey was invalid value"
) from exc
options_authr_selection_require_rkey = options_authr_selection.get("requireResidentKey")
mapped_require_rkey = False
if options_authr_selection_require_rkey is not None:
if not isinstance(options_authr_selection_require_rkey, bool):
raise InvalidJSONStructure(
"Options authenticatorSelection requireResidentKey was invalid boolean"
)
mapped_require_rkey = options_authr_selection_require_rkey
options_authr_selection_uv = options_authr_selection.get("userVerification")
mapped_user_verification = UserVerificationRequirement.PREFERRED
if options_authr_selection_uv is not None:
try:
mapped_user_verification = UserVerificationRequirement(options_authr_selection_uv)
except ValueError as exc:
raise InvalidJSONStructure(
"Options authenticatorSelection userVerification was invalid value"
) from exc
mapped_authenticator_selection = AuthenticatorSelectionCriteria(
authenticator_attachment=mapped_attachment,
resident_key=mapped_rkey,
require_resident_key=mapped_require_rkey,
user_verification=mapped_user_verification,
)
"""
Check challenge is present
"""
options_challenge = json_val.get("challenge")
if not isinstance(options_challenge, str):
raise InvalidJSONStructure("Options missing required challenge")
"""
Check pubKeyCredParams
"""
options_pub_key_cred_params = json_val.get("pubKeyCredParams")
if not isinstance(options_pub_key_cred_params, list):
raise InvalidJSONStructure("Options pubKeyCredParams was invalid value")
try:
mapped_pub_key_cred_params = [
PublicKeyCredentialParameters(
alg=COSEAlgorithmIdentifier(param["alg"]), type="public-key"
)
for param in options_pub_key_cred_params
]
except ValueError as exc:
raise InvalidJSONStructure("Options pubKeyCredParams entry had invalid alg") from exc
"""
Check excludeCredentials
"""
options_exclude_credentials = json_val.get("excludeCredentials")
mapped_exclude_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None
if isinstance(options_exclude_credentials, list):
mapped_exclude_credentials = []
for cred in options_exclude_credentials:
_cred_id = cred.get("id")
if not isinstance(_cred_id, str):
raise InvalidJSONStructure("Options excludeCredentials entry missing required id")
_mapped = PublicKeyCredentialDescriptor(id=base64url_to_bytes(_cred_id))
_transports = cred.get("transports")
if _transports is not None:
if not isinstance(_transports, list):
raise InvalidJSONStructure(
"Options excludeCredentials entry transports was not list"
)
try:
_mapped.transports = [
AuthenticatorTransport(_transport) for _transport in _transports
]
except ValueError as exc:
raise InvalidJSONStructure(
"Options excludeCredentials entry transports had invalid value"
) from exc
mapped_exclude_credentials.append(_mapped)
"""
Check timeout
"""
options_timeout = json_val.get("timeout")
mapped_timeout = None
if isinstance(options_timeout, int):
mapped_timeout = options_timeout
try:
registration_options = PublicKeyCredentialCreationOptions(
rp=PublicKeyCredentialRpEntity(
id=options_rp_id,
name=options_rp_name,
),
user=PublicKeyCredentialUserEntity(
id=base64url_to_bytes(options_user_id),
name=options_user_name,
display_name=options_user_display_name,
),
attestation=mapped_attestation,
authenticator_selection=mapped_authenticator_selection,
challenge=base64url_to_bytes(options_challenge),
pub_key_cred_params=mapped_pub_key_cred_params,
exclude_credentials=mapped_exclude_credentials,
timeout=mapped_timeout,
)
except Exception as exc:
raise InvalidRegistrationOptions(
"Could not parse registration options from JSON data"
) from exc
return registration_options
|