attestation_object contains authenticator_data authenticator_data contains credential_public_key ( credential_key_pair = [ credential_private_key, credential_public_key ] ) 0x30,0x09,0x02,0x01,0x07,0x02,0x01,0x08,0x02,0x01,0x09, ```js // var aa = Uint8Array.from( "hello world", c => c.charCodeAt(0) ); var aa /** :Uint8Array */ = new TextEncoder().encode( "hello world" ); var a2 /** :Uint8Array */ = new Uint8Array( aa.buffer, 2, 7 ); // "llo wor" console.log( a2.buffer === aa.buffer ); // true var bb /** :Uint8Array */ = aa.slice( 3, 8 ); // "lo wo" console.log( aa.buffer === bb.buffer ); // false console.log( "`aa` and `bb` have different underlying buffers due to `buffer.slice()`" ); console.log( "use `new Uint8Array( buffer, offset, length )` to create a view without a new buffer" ); aa // Uint8Array(11) [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, buffer: ArrayBuffer(11), byteLength: 11, byteOffset: 0, length: 11, Symbol(Symbol.toStringTag): 'Uint8Array'] a2 // Uint8Array(7) [108, 108, 111, 32, 119, 111, 114, buffer: ArrayBuffer(11), byteLength: 7, byteOffset: 2, length: 7, Symbol(Symbol.toStringTag): 'Uint8Array'] bb // Uint8Array(5) [108, 111, 32, 119, 111, buffer: ArrayBuffer( 5), byteLength: 5, byteOffset: 0, length: 5, Symbol(Symbol.toStringTag): 'Uint8Array'] new Uint8Array( xx ); // Uint8Array(14) [108, 108, 111, 32, 119, 111, 114, 108, 108, 111, 32, 119, 111, 114, buffer: ArrayBuffer(14), byteLength: 14, byteOffset: 0, length: 14, Symbol(Symbol.toStringTag): 'Uint8Array'] var xx /** :ArrayBuffer */ = await new Blob( [ a2, a2 ] ).arrayBuffer(); // "llo worllo wor" console.log( new Uint8Array( xx ) ); console.log( new TextDecoder().decode( aa ) ); // "hello world" console.log( new TextDecoder().decode( a2 ) ); // "llo wor" console.log( new TextDecoder().decode( bb ) ); // "lo wo" console.log( new TextDecoder().decode( xx ) ); // "llo worllo wor" // XXX new Uint8Array( [ a2, a2 ] ) // [ 0, 0 ] // or new Uint8Array( [ ...a2, ...a2 ] ) // [ 108,108,111,32,119,111,114 , 108,108,111,32,119,111,114 ] ``` .. avoid creating intermediate *ArrayBuffers* for new *Blobs*, .. just use u8 views on whatever array, like `a2` above `[ ...bytes ].map( x => x.toString(16).padStart( 2, '0' ) )` aside: character values & unicode... `str.charCodeAt(0)` -vs- `str.codePointAt(0)` > UTF-16 characters, Unicode code points, and grapheme clusters. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters ---- # Subject Public Key Info https://www.rfc-editor.org/rfc/rfc5480 ## Fields (ASN.1) *inside X.509 certificate* ..? ```asn.1 SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } ``` * `algorithm` is the algorithm identifier and parameters for the ECC public key. * `subjectPublicKey` is the ECC public key. See [Section 2.2]. * `ECPoint ::= OCTET STRING` * first byte is enum of { `0x04`=*uncompressed*, `0x02`=*compressed*, `0x03`=*compressed* } * non-[ `0x02`,`0x03`,`0x04` ] byte is invalid /to-be-rejected ``` AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL } ECParameters ::= CHOICE { namedCurve OBJECT IDENTIFIER } ``` [Section 2.2]: https://www.rfc-editor.org/rfc/rfc5480#section-2.2 ---- a public key example, specifically, the OBJECT IDENTIFIERS ```js /** * @param { { "1":number, "3":number, "-1":number, "-2":Uint8Array, "-3":Uint8Array } } cose * @return { Uint8Array } subjectPublicKeyInfo */ function get_spki_from_x_y( cose ) { console.assert( cose["-1"] == 1 ); const x = cose["-2"]; const y = cose["-3"]; // originally from an ArrayBuffer, from `credential.response.getPublicKey()` const subjectPublicKeyInfo = new Uint8Array( [ 0x30,0x59, // ASN.1 tag,length - SEQUENCE, 89 bytes 0x30,0x13, // ASN.1 tag,length - SEQUENCE, 19 bytes 0x06,0x07, // ASN.1 tag,length - OBJECT IDENTIFIER, 7 bytes 0x2a,0x86,0x48,0xce,0x3d,0x02,0x01, // "1.2.840.10045.2.1" - ecPublicKey 0x06,0x08, // ASN.1 tag,length - OBJECT IDENTIFIER, 8 bytes 0x2a,0x86,0x48,0xce,0x3d,0x03,0x01,0x07, // "1.2.840.10045.3.1.7" - ansix9p256r1 0x03,0x42, // ASN.1 tag,length - BIT STRING, 66 bytes 0x00,// BIT STRING - 0 unused bits 0x04, // SPKI "Subject Public Key Info" - uncompressed // ansix9p256r1 - x 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ansix9p256r1 - y 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, ] ); const len = 91; console.assert( len == subjectPublicKeyInfo.length ); let write_offset = len - 32*2; const x32 = 32 < x.length ? x.slice(-32) : x; // max 32 bytes write_offset += 32 - x32.length; // left-pad zeros subjectPublicKeyInfo.set( x32, write_offset ); write_offset += x32.length; const y32 = 32 < y.length ? y.slice(-32) : y; // max 32 bytes write_offset += 32 - y32.length; // left-pad zeros subjectPublicKeyInfo.set( y32, write_offset ); write_offset += y32.length; console.assert( len == write_offset ); return subjectPublicKeyInfo; } ``` ```js [ Math.floor( 0x2a / 40 ), 0x2a % 40, ( 0x7f & 0x86 ) * 128**1 + ( 0x7f & 0x48 ) * 128**0 , ( 0x7f & 0xce ) * 128**1 + ( 0x7f & 0x3d ) * 128**0 , ( 0x7f & 0x02 ) * 128**0 , ( 0x7f & 0x01 ) * 128**0 , ] // [1, 2, 840, 10045, 2, 1] // "1.2.840.10045.2.1" // { iso(1) member-body(2) us(840) ansi-x962(10045) keyType(2) ecPublicKey(1) } // id-ecPublicKey OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } // https://www.rfc-editor.org/rfc/rfc5480#section-2.1.1 [ Math.floor( 0x2a / 40 ), 0x2a % 40, // after the first two, the high/sign bit indicates breaks ( 0x7f & 0x86 ) * 128**1 + ( 0x7f & 0x48 ) * 128**0 , ( 0x7f & 0xce ) * 128**1 + ( 0x7f & 0x3d ) * 128**0 , ( 0x7f & 0x03 ) * 128**0 , ( 0x7f & 0x01 ) * 128**0 , ( 0x7f & 0x07 ) * 128**0 , ] // [1, 2, 840, 10045, 3, 1, 7] // "1.2.840.10045.3.1.7" // https://www.itu.int/wftp3/Public/t/fl/itu-t/x/x894/2018-cor1/ANSI-X9-62.html /* -- ANSI-X9-62 {iso(1) member-body(2) us(840) 10045 module(0) 2} ansi-X9-62 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) 10045 } -- Arc in X9.62 for naming EC domain parameters that are not named elsewhere ellipticCurve OBJECT IDENTIFIER ::= { ansi-X9-62 curves(3) } -- Arc in X9.62 for identifying prime order elliptic curve domain parameters primeCurve OBJECT IDENTIFIER ::= { ellipticCurve prime(1) } -- Named EC domain parameters in X9.62 ansix9p256r1 OBJECT IDENTIFIER ::= {primeCurve 7 } */ // { iso(1) member-body(2) us(840) ansi-x962(10045) curves(3) prime(1) primeCurve(7) } // ansix9p256r1 == "secg/secp256r1" == "x962/prime256v1" == "nist/P-256" // https://www.secg.org/SEC2-Ver-1.0.pdf ``` https://webauthn.passwordless.id/demos/playground.html https://github.com/passwordless-id/webauthn/edit/main/README.md