Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WebUSBでレイヤーが低まるWeb開発(Shibuya.XSS版)

Fadis
December 12, 2017

 WebUSBでレイヤーが低まるWeb開発(Shibuya.XSS版)

Chrome 61から使えるようになったWebUSB APIを使ってUSBデバイスと会話する方法を解説します。
これは2017年12月13日に行われた Shibuya.XSS techtalk #10 の発表資料です。
これは2017年9月23日に行われた 第3回 カーネル/VM探検隊@北陸での発表資料に加筆修正を行なったものです。

Fadis

December 12, 2017
Tweet

More Decks by Fadis

Other Decks in Programming

Transcript

  1. WebUSBͰ
    ௿·ΔWeb։ൃ
    ϨΠϠʔ͕
    NAOMASA MATSUBAYASHI
    ιʔείʔυ: https://goo.gl/cijdrQ
    (Shibuya.XSS൛)

    View Slide

  2. WebUSB API
    http://wicg.github.io/webusb

    View Slide

  3. ϗετʹ઀ଓ͞ΕͨUSBσόΠεͱϒϥ΢β͕
    OSͷσόΠευϥΠόΛհͣ͞ʹ
    ௚઀ձ࿩͢ΔͨΊͷJavaScriptͷAPI
    Χʔωϧ
    ϒϥ΢β 64#σόΠε
    WebUSB API
    libusbΛWeb͔Βୟ͚ΔΑ͏ʹͳͬͨͱࢥ͑͹
    ͍͍͍ͩͨ͋ͬͯΔ

    View Slide

  4. σόΠεΛऔಘ͢Δ
    return navigator.usb.requestDevice(
    { 'filters': [
    { 'vendorId': 0x1234, 'productId': 0x5678 }
    ] }
    ).then(device_ => {
    device = device_;
    return connect();
    }).catch(error => {
    console.log('઀ଓΤϥʔ: ' + error);
    });
    navigator.usb.requestDevieͰ
    ࢦఆͨ͠ϕϯμIDͱϓϩμΫτIDΛ࣋ͭ
    USBσόΠε΁ͷ઀ଓΛཁٻ͢Δ

    View Slide

  5. σόΠεΛऔಘ͢Δ
    ͜Μͳϓϩϯϓτ͕ग़ͯ͘Δ
    Ϣʔβ͸Webϖʔδʹ৮Βͤͯྑ͍σόΠεΛબͿ

    View Slide

  6. USBσόΠε
    σόΠε
    ΤϯυϙΠϯτ0
    ΤϯυϙΠϯτ1
    ίϯϑΟάϨʔγϣϯ0
    ΠϯλʔϑΣʔε0
    ΠϯλʔϑΣʔε1
    ΠϯλʔϑΣʔε2
    ΤϯυϙΠϯτ0
    ΤϯυϙΠϯτ1
    ΤϯυϙΠϯτ2
    ΤϯυϙΠϯτ3

    View Slide

  7. USBσόΠε
    σόΠε
    ΤϯυϙΠϯτ0
    ΤϯυϙΠϯτ1
    ίϯϑΟάϨʔγϣϯ0
    ΠϯλʔϑΣʔε0
    ΠϯλʔϑΣʔε1
    ΠϯλʔϑΣʔε2
    ΤϯυϙΠϯτ0
    ΤϯυϙΠϯτ1
    ΤϯυϙΠϯτ2
    ΤϯυϙΠϯτ3
    σʔλΛૹड৴͢Δͱ͖ʹ͸
    ͲͷΤϯυϙΠϯτͰձ࿩͢Δ͔Λࢦఆ͢Δ

    View Slide

  8. USBσόΠε
    σόΠε
    ΤϯυϙΠϯτ0
    ΤϯυϙΠϯτ1
    ίϯϑΟάϨʔγϣϯ0
    ΠϯλʔϑΣʔε0
    ΠϯλʔϑΣʔε1
    ΠϯλʔϑΣʔε2
    ΤϯυϙΠϯτ0
    ΤϯυϙΠϯτ1
    ΤϯυϙΠϯτ2
    ΤϯυϙΠϯτ3
    ΞϓϦέʔγϣϯ͸
    ΠϯλʔϑΣʔε୯ҐͰΤϯυϙΠϯτΛ઎༗͢Δ

    View Slide

  9. σόΠεʹσʔλΛૹΔ/໯͏
    device.controlTransferOut({
    'requestType': 'class',
    'recipient': 'interface',
    'request': 0x22,
    'value': 0x01,
    'index': 0x00
    }).then(result => {

    }
    ίϯτϩʔϧసૹ
    device.controlTransferIn({
    'requestType': 'standard',
    'recipient': 'device',
    'request': 0x06,
    'value': 0x0100,
    'index': 0x0000
    }, 0x12).then(result => {

    }
    σόΠεͷ৘ใͷऔಘ΍
    σόΠεͷॳظԽΛߦ͏

    View Slide

  10. ௨৴Ͱ͖Δঢ়ଶʹ͢Δ
    return device.open().then(() => {
    if(device.configuration === null) {
    return device.selectConfiguration(1);
    }
    }).then(() => {
    return device.claimInterface(1);
    }).then(() => {
    return device.claimInterface(0);
    });
    configurationΛબͿ
    USBσόΠε͸ෳ਺ͷػೳΛఏڙ͍ͯ͠Δ͜ͱ͕͋Δ
    ͲͷػೳΛ࢖͏͔ΛબͿͷ͕configurationͷબ୒
    ඞཁͳΠϯλʔϑΣʔεΛ઎༗͢Δ

    View Slide

  11. σόΠεʹσʔλΛૹΔ/໯͏
    device.transferIn(1, 1024).then(result => {
    something_on_recieved( result.data );
    }
    σόΠε͔Βड৴
    σόΠεʹૹ৴
    ΤϯυϙΠϯτID ड৴͢ΔόΠτ਺
    PromiseͰड৴݁ՌͱDataView͕ฦͬͯདྷΔ
    device.transferOut(2, data).then(result => {
    something_on_sent();
    }
    ΤϯυϙΠϯτID
    ૹ৴͢ΔArrayBuffer
    PromiseͰૹ৴݁Ռ͕ฦͬͯདྷΔ

    View Slide

  12. CDC ACM γϦΞϧ௨৴
    Webϒϥ΢βͷதͱ֎Ͱձ࿩ͯ͠ΈΑ͏

    View Slide

  13. USB GadgetυϥΠό
    ࢲ͸64#֎෇͚
    ϋʔυσΟεΫͰ͢
    ϋʔυσΟεΫ͕དྷͨ
    USBσόΠεͷϑϦΛ͢ΔυϥΠό

    View Slide

  14. dummy_udc
    ϋʔυσΟεΫ͕དྷͨ
    Ծ૝తͳUSBϗετͱσόΠεͷ૊Λ࡞Δ
    ࢲ͸64#֎෇͚
    ϋʔυσΟεΫͰ͢

    View Slide

  15. $ modprobe libcomposite
    $ modprobe dummy_hcd
    $ cd /sys/kernel/config/usb_gadget
    $ mkdir g1
    $ cd g1
    $ mkdir functions/acm.g1
    $ mkdir configs/c.1
    $ ln -s functions/acm.g1 configs/c.1
    $ echo 0x1234 >idVendor
    $ echo 0x5678 >idProduct
    $ echo dummy_udc.0 >UDC
    $ sleep 1
    $ modprobe -r cdc_acm
    $ agetty 115200 ttyGS0
    LinuxͷUSB GadgetͰ
    CDC ACMͷγϦΞϧ௨৴σόΠεΛ࡞Γ
    σόΠεଆͰgetty͢Δ
    ϗετଆυϥΠό͸Ξϯϩʔυ͓ͯ͘͠

    View Slide

  16. CDC ACM
    σόΠε
    ίϯϑΟάϨʔγϣϯ0
    CDC ACM Comm(0)
    CDC ACM Data(1)
    ੍ޚ৴߸(3)
    σʔλೖྗ(1)
    σʔλग़ྗ(2)
    σʔλೖग़ྗͷͨΊͷόϧΫΤϯυϙΠϯτΛ
    උ͑ΔΠϯλʔϑΣʔε͕1ͭ
    ੍ޚ৴߸ΛૹΔͨΊͷׂΓࠐΈΤϯυϙΠϯτΛ
    උ͑ΔΠϯλʔϑΣʔε͕1ͭ

    View Slide

  17. function flush() {
    writing = true;
    let buffer_ = buffer;
    buffer = '';
    device.transferOut(2, textEncoder.encode(buffer_)).then(
    result => {
    writing = false;
    if( buffer.length != 0 ) { flush(); }
    }).catch(error => {
    console.log(‘ૹ৴Τϥʔ: ' + error);
    writing = false;
    if( buffer.length != 0 ) { flush(); }
    });
    }
    t.open( document.getElementById('terminal') );
    t.on('key', function (key, ev) {
    if(device !== undefined) {
    if( !writing ) {
    buffer += key;
    flush();
    }
    else { buffer += key; }
    }
    });
    xterm.jsʹΩʔೖྗ͕͋ͬͨΒ
    ΤϯυϙΠϯτʹೖྗ಺༰Λྲྀ͢

    View Slide

  18. let readLoop = () => {
    if( device ) {
    device.transferIn(1, 1024).then(result => {
    let textDecoder = new TextDecoder();
    t.write(textDecoder.decode(result.data));
    readLoop();
    }, error => {
    console.log( error );
    readLoop();
    });
    }
    };
    ΤϯυϙΠϯτ1Ͱ͸
    σόΠε͔ΒͷσʔλΛ଴ͪड͚
    Կ͔ड͚औͬͨΒxterm.jsʹྲྀ͢

    View Slide

  19. USBΛ௨ͬͯ֎ͷੈքʹඈͼग़͢Α!
    http://fadis.github.io/webusb/minimal/

    View Slide

  20. USBϚεετϨʔδ
    USB֎෇͚ϋʔυσΟεΫ΍
    USBϑϥογϡϝϞϦͱձ࿩ͯ͠ΈΑ͏

    View Slide

  21. ͜ͷUSBϑϥογϡϝϞϦΛ
    ϒϥ΢β͔ΒಡΉ
    $ modprobe -r uas
    $ modprobe -r mass_storage
    ·ͣΧʔωϧ͕अຐΛ͠ͳ͍Α͏ʹ͓ͯ͘͠

    View Slide

  22. Command Block Wrapper
    SCSIίϚϯυ
    Command State Wrapper
    ίϚϯυͷ࣮ߦ݁Ռ
    ίϚϯυʹ෇ਵ͢Δ
    σʔλΛૹड৴
    Bulk Only Transport
    ࡉ͔͍ετϨʔδͷૢ࡞ํ๏͸SCSIͦͷ··

    View Slide

  23. σόΠε
    ίϯϑΟάϨʔγϣϯ0
    MSC(0)
    σʔλೖྗ(1)
    σʔλग़ྗ(2)
    σʔλೖग़ྗͷͨΊͷόϧΫΤϯυϙΠϯτΛ
    උ͑ΔΠϯλʔϑΣʔε͕1͚ͭͩ
    Bulk Only Transport

    View Slide

  24. Command Block Wrapper
    let cbw = ( tag, len, dir, command ) => {
    let data = new Uint8Array( 15 + 16 );
    data.set([
    0x55, 0x53, 0x42, 0x43, // USBC
    tag & 0xFF,
    ( tag >> 8 ) & 0xFF,
    ( tag >> 16 ) & 0xFF,
    ( tag >> 24 ) & 0xFF, // tag
    len & 0xFF,
    ( len >> 8 ) & 0xFF,
    ( len >> 16 ) & 0xFF,
    ( len >> 24 ) & 0xFF, // len
    dir << 7, // flags
    0, // LUN
    command.byteLength // command length
    ], 0);
    data.set( command, 15 );
    return device.transferOut( 2, data );
    }
    4$4*ίϚϯυʹ
    $#8ͷ
    ϔομΛ͚ͭͯ
    σόΠεʹ౤͛Δ

    View Slide

  25. Command State Wrapper
    let csw = () => {
    return device.transferIn( 1, 13 ).then(
    result => {
    let state = result.data.getUint8( 12 );
    return state == 0;
    }
    );
    }
    ίϚϯυͷ࣮ߦ݁ՌΛσόΠε͔Βड͚औΔ

    View Slide

  26. ετϨʔδΛಡΉͷʹඞཁͳSCSIίϚϯυ
    INQUIRY
    TEST UNIT READY
    State?
    READ CAPACITY(10)
    READ(10)
    OK
    *
    -6/ʹσόΠε͕
    ͋Δ͜ͱΛ֬ೝ
    σόΠε͕
    ར༻ՄೳʹͳΔͷΛ଴ͭ
    σόΠεͷ༰ྔͱ
    ηΫλαΠζΛऔಘ
    σόΠε͔Β
    σʔλΛಡΈग़͢

    View Slide

  27. let inquiry = () => {
    let command = new Uint8Array([
    // command LUN reserved reserved size reserved
    0x12, 0, 0, 0, 36, 0
    ]);
    return cbw( 1, 36, 1, command ).then( result => {
    return device.transferIn( 1, 36 );
    }).then( result => {
    return result.data;
    }).then( data => {
    return csw().then( stat => {
    return { 'status': stat, 'data': data }
    });
    });
    }
    ඞཁͳSCSIίϚϯυΛ࣮૷͍ͯ͘͠

    View Slide

  28. let test_unit_ready = () => {
    let command = new Uint8Array([
    // command LUN
    0x00, 0
    ]);
    return cbw( 1, 0, 1, command ).then( result => {
    return csw();
    }).then( stat => {
    if( stat ) {
    console.log( 'σόΠε͕ར༻ՄೳʹͳΓ·ͨ͠' );
    return true;
    }
    else {
    console.log( 'σόΠε͕ར༻ՄೳʹͳΔͷΛ଴͍ͬͯ·͢' );
    return async_sleep( 1000 ).then( test_unit_ready );
    }
    });
    }
    ඞཁͳSCSIίϚϯυΛ࣮૷͍ͯ͘͠

    View Slide

  29. let read_capacity = () => {
    let command = new Uint8Array([
    0x25, 0 // command LUN
    ]);
    return cbw( 1, 8, 1, command ).then( result => {
    return device.transferIn( 1, 8 )
    }).then( result => {
    let last_lba = result.data.getUint32( 0 );
    let block_len = result.data.getUint32( 4 );
    return {
    'last_logical_block_address': last_lba,
    'block_length': block_len
    };
    }).then( data => {
    return csw().then( stat => {
    return { 'status': stat, 'data': data }
    });
    });
    };
    ඞཁͳSCSIίϚϯυΛ࣮૷͍ͯ͘͠

    View Slide

  30. let read = ( offset, count ) => {
    let command = new Uint8Array([
    0x28, 0, // command LUN
    ( offset >> 24 ) & 0xFF, ( offset >> 16 ) & 0xFF,
    ( offset >> 8 ) & 0xFF, offset & 0xFF, // LBA
    0, // reserved
    ( count >> 8 ) & 0xFF, count & 0xFF, // length
    0 // reserved
    ]);
    let size = sector_size * count;
    return cbw( 1, size, 1, command ).then( result => {
    return device.transferIn( 1, sector_size * count )
    }).then( result => {
    return result.data;
    }).then( data => {
    return csw().then( stat => {
    return { 'status': stat, 'data': data }
    });
    });
    };
    ඞཁͳSCSIίϚϯυΛ࣮૷͍ͯ͘͠

    View Slide

  31. ϚελʔϒʔτϨίʔυ
    ύʔςΟγϣϯ
    ςʔϒϧͷ
    ͭΊͷΤϯτϦ
    ϒʔτγάχνϟ
    -#"͔Β
    ϒϩοΫΛऔಘ
    55 aa

    View Slide

  32. BIOSύϥϝʔλϒϩοΫ
    FAT32ͷϔομ
    46 41 54 33 32 20 20 20
    F A T 3 2 _ _ _

    View Slide

  33. FAT32
    let load_fat32 = partition => {
    return read( partition.at, 1 ).then( result => {
    let cluster_size = result.data.getUint8( 13 );
    let reserved_sector_size =
    result.data.getUint16( 14, true );
    let num_fats = result.data.getUint8( 16 );
    let rde_size = result.data.getUint16( 17, true );
    let fat_size = result.data.getUint32( 36, true );
    let rde = result.data.getUint32( 44, true );
    let fat_lba = partition.at + reserved_sector_size;
    return read( fat_lba, fat_size ).then( result => {
    let len = result.data.byteLength / 4;
    let fat = new Uint32Array( len );
    for( let i = 0; i != len; i++ ) {
    fat[ i ] = result.data.getUint32( i * 4, true );
    }
    let clusters_lba = fat_lba + num_fats * fat_size;
    let fsinfo = {
    'cluster_size': cluster_size, 'fat': fat,
    'clusters_lba': clusters_lba, 'rootdir_entry': rde
    BIOSύϥϝʔλϒϩοΫ͔Β
    ϑΝΠϧγεςϜΛಡΉͷʹඞཁͳ஋Λऔಘ
    ΫϥελαΠζ
    FATͷ਺ͱαΠζͱ։࢝ηΫλ
    ϧʔτσΟϨΫτϦ͕ॻ͔Ε͍ͯΔҐஔ

    View Slide

  34. FAT32
    result.data.getUint16( 14, true );
    let num_fats = result.data.getUint8( 16 );
    let rde_size = result.data.getUint16( 17, true );
    let fat_size = result.data.getUint32( 36, true );
    let rde = result.data.getUint32( 44, true );
    let fat_lba = partition.at + reserved_sector_size;
    return read( fat_lba, fat_size ).then( result => {
    let len = result.data.byteLength / 4;
    let fat = new Uint32Array( len );
    for( let i = 0; i != len; i++ ) {
    fat[ i ] = result.data.getUint32( i * 4, true );
    }
    let clusters_lba = fat_lba + num_fats * fat_size;
    let fsinfo = {
    'cluster_size': cluster_size, 'fat': fat,
    'clusters_lba': clusters_lba, 'rootdir_entry': rde
    };
    return load_fat32file(
    fsinfo, fsinfo.rootdir_entry, 0
    ).then( raw_root_dir => {
    let root_dir =
    parse_fat32directory( raw_root_dir );
    FATʹ͸
    ࠓಡΜͰ͍ΔΫϥελͷ࣍ʹಡΉ΂͖Ϋϥελ͕
    Ͳ͔͕͜ه࿥͞Ε͍ͯΔ
    FATΛಡΉ

    View Slide

  35. FAT32
    let fat = new Uint32Array( len );
    for( let i = 0; i != len; i++ ) {
    fat[ i ] = result.data.getUint32( i * 4, true );
    }
    let clusters_lba = fat_lba + num_fats * fat_size;
    let fsinfo = {
    'cluster_size': cluster_size, 'fat': fat,
    'clusters_lba': clusters_lba, 'rootdir_entry': rde
    };
    return load_fat32file(
    fsinfo, fsinfo.rootdir_entry, 0
    ).then( raw_root_dir => {
    let root_dir =
    parse_fat32directory( raw_root_dir );
    fsinfo[ 'rootdir' ] = root_dir;
    return fsinfo;
    });
    });
    });
    };
    ϧʔτσΟϨΫτϦΛಡΉ

    View Slide

  36. let get_fat32clusters_reversed =
    ( fsinfo, head ) => {
    let next = fsinfo.fat[ head ] & 0x0FFFFFFF;
    if( next >= 0x00000002 && next <= 0x0ffffff6 ) {
    let tail =
    get_fat32clusters_reversed( fsinfo, next );
    tail.push( head );
    return tail;
    }
    else return [ head ];
    };
    ΫϥελνΣΠϯ
    FAT͔ΒಡΉඞཁ͕͋ΔΫϥελΛௐ΂ͯ

    View Slide

  37. let get_fat32clusters = ( fsinfo, head ) => {
    let clusters = get_fat32clusters_reversed( fsinfo, head );
    clusters.reverse();
    let chunks = [ { 'at': clusters[ 0 ], 'length': 1 } ];
    for( let i = 1; i != clusters.length; i++ ) {
    if( clusters[ i - 1 ] + 1 == clusters[ i ] ) {
    chunks[ chunks.length - 1 ].length++;
    }
    else {
    chunks.push( { 'at': clusters[ i ], 'length': 1 } );
    }
    }
    return chunks;
    }
    ΫϥελνΣΠϯ
    ࿈ଓͨ͠Ϋϥελ͸·ͱΊͯ

    View Slide

  38. let load_fat32cluster = ( fsinfo, chunks, index, data ) => {
    let lba = fsinfo.clusters_lba +
    ( chunks[ index ].at - 2 ) * fsinfo.cluster_size;
    return read(
    lba, chunks[ index ].length * fsinfo.cluster_size
    ).then(
    result => {
    data.push( result.data );
    if( index + 1 < chunks.length ) {
    return load_fat32cluster(
    fsinfo, chunks, index + 1, data
    );
    }
    else return data;
    });
    }
    ΫϥελνΣΠϯ
    READ(10)

    View Slide

  39. let parse_fat32directory = ( data ) => {
    let files = [];
    let lfn = [];
    for( let offset = 0; offset != data.byteLength; offset +=
    32 ) {
    let entry = data.subarray( offset, offset + 32 );
    let attribute = entry[ 11 ];
    let sfn_head = entry[ 0 ];
    if( sfn_head == 0x00 ) {
    break;
    }
    else if( sfn_head == 0xE5 ) {
    continue;
    }
    else if( attribute == 0x0F ) {
    let fragment = new Uint8Array( 26 );
    fragment.set( entry.subarray( 1, 11 ), 0 );
    fragment.set( entry.subarray( 14, 26 ), 10 );
    fragment.set( entry.subarray( 28, 32 ), 22 );
    lfn.push( fragment );
    σΟϨΫτϦΤϯτϦ
    ͦΜͳʹෳࡶ͡Όͳ͍ͷͰؾ߹͍Ͱύʔε͢Δ

    View Slide

  40. ϒϥ΢βͰUSBϑϥογϡϝϞϦΛಡΉΑ!
    http://fadis.github.io/webusb/storage/

    View Slide

  41. USBϑϥογϡϝϞϦ͔ΒಡΜͩը૾Λ
    Webϖʔδʹදࣔ͢ΔΑ!
    http://fadis.github.io/webusb/storage/

    View Slide

  42. WebUSBͷηΩϡϦςΟ
    WebUSB͸USBσόΠεʹର͢Δ
    ͋ΒΏΔૢ࡞Λߦ͏ݖݶΛ
    ϦϞʔτ͔ΒඈΜͰདྷͨJavaScriptʹ༩͑Δ
    σόΠεʹΑͬͯ͸JavaScript͸
    σόΠεΛ৐ͬऔΔࣄ΋ഁյ͢Δࣄ΋Ͱ͖Δ
    ࠷௿Ͱ΋JavaScriptͷग़ॴ͕
    อূ͞Ε͍ͯͳ͍ͱාͯ͘ར༻Ͱ͖ͳ͍

    View Slide

  43. WebUSBͷηΩϡϦςΟ
    WebUSB͸SecureContextͰͷΈ࢖༻Ͱ͖Δ
    https://www.w3.org/TR/secure-contexts/
    TLS
    ҉߸Խ͞Ε͍ͯͯ
    εΫϦϓτΛڬΊͳ͍
    ฏจͰҾͬுΒΕͨ
    εΫϦϓτΛվ᜵ͯ͠΋
    ͦ͜Ͱ͸WebUSB͸ಈ͔ͳ͍
    ࠷ॳʹTLSͰऔಘͨ͠Webϖʔδ͔Β
    TLSΛհͯ͠ḷΓண͚ΔεΫϦϓτ͚͕ͩ
    USBσόΠεΛૢ࡞Ͱ͖Δ
    ͱ͜ΖͰͦͷWebϖʔδࣗମ͸৴པͰ͖Δ?

    View Slide

  44. WebUSBͷηΩϡϦςΟ
    https://example.org/
    ͳΜ͚ͩͲ
    USBσόΠεΛ࢖ΘͤͯΑ
    https://example.com/
    Ͱ࢖ΘΕΔҝʹ࡞ΒΕͨ
    WebUSBରԠσόΠεͰ͢
    μϝ
    ౰ॳWebUSB͸σόΠεʹ৘ใΛຒΊΔࣄͰ
    ҙਤ͠ͳ͍Webϖʔδʹ
    σόΠεΛ࢖ΘΕͳ͍Α͏ʹ͠Α͏ͱͨ͠

    View Slide

  45. WebUSB Platform Capability Descriptor
    WebUSBରԠσόΠεͰ͋Δ͜ͱΛࣔ͢σεΫϦϓλ
    https://wicg.github.io/webusb/

    View Slide

  46. Get URL
    σόΠεͷURLΛऔಘ͢Δҝͷ৽͍͠ϦΫΤετ
    ͜ͷURLͱಉ͡υϝΠϯͷWebϖʔδ͔Βͷ
    WebUSBϦΫΤετ͔͠ड͚෇͚ͳ͍
    https://wicg.github.io/webusb/

    View Slide

  47. WebαΠτAͰ
    ೝূ͢ΔͨΊͷ
    ΧʔυϦʔμʔ
    WebαΠτB༻Ͱ
    ೝূ͢ΔͨΊͷ
    ΧʔυϦʔμʔ
    WebαΠτC༻Ͱ
    ೝূ͢ΔͨΊͷ
    ΧʔυϦʔμʔ
    ͜Ε͸ศརͰ͸ͳ͍

    View Slide

  48. ౰ॳσόΠεʹURLຒΊࠐ΋͏ͱࢥ͚ͬͨͲ
    σόΠεͷϕϯμʔʹରԠͯ͠΋Β͏ͷ೉͍͠͠
    αʔυύʔςΟ͕σόΠε࢖͑ͳ͘ͳΔ͔Β΍Ίͨ
    ϢʔβʹϓϩϯϓτͰ֬ೝͱͬͯΔ͠
    ͦΕ͚ͩͰे෼ͳΜ͡Όͳ͍͔ͳ
    https://wicg.github.io/webusb/

    View Slide

  49. WebUSBͷηΩϡϦςΟ
    ͜͏ͯ͠σόΠε઀ଓϓϩϯϓτҎ֎ʹ
    ѱҙ͋ΔυϥΠόͷ࣮ߦΛ๷͙ज़͕ͳ͍
    WebUSB͕҆ఆ൛ͷChromeʹ࣮૷͞Εͨ
    https://developers.google.com/web/updates/2017/09/nic61

    View Slide

  50. WebUSBͷηΩϡϦςΟ
    ͜ͷϓϩϯϓτͷҙຯ͸
    Ӿཡதͷ8FCϖʔδ͕ಘମͷ஌Εͳ͍υϥΠόͰ
    64#σόΠεΛ޷͖উख͢Δࣄʹಉҙ͠·͔͢

    View Slide

  51. XSSͷ؍఺͔Βͷߟ࡯
    WebUSBࣗମʹ
    XSSͷҾ͖ۚͱͳΔ੬ऑੑ͸ݟ͔͍ͭͬͯͳ͍
    WebUSBͰσόΠεͷར༻ΛڐՄͨ͠Webϖʔδʹ
    XSS͕Մೳͳ੬ऑੑ͕͋ͬͨ৔߹
    USBσόΠε͕߈ܸऀͷखʹତͪΔڪΕ͕͋Δ
    (ྫ͑͹ѱҙ͋ΔϑΝʔϜ΢ΣΞΛ࢓ࠐ·ΕΔͱ͔)
    ͱ͸͍͑WebUSBΛୟ͘ʹ͸
    Secure Context಺Ͱ࣮ߦ͞ΕΔඞཁ͕͋Δҝ
    ͜ΕΛར༻͢ΔXSS͸༰қͰ͸ͳ͍ͱࢥΘΕΔ

    View Slide

  52. ChromeͷWebUSBͷ࣮૷͸
    Χʔωϧ͔ΒσόΠεΛσλον͠ͳ͍
    ΧʔωϧυϥΠό͕Ѳ͍ͬͯΔσόΠε͸
    Ϣʔβ͕໌ࣔతʹΧʔωϧ͔Βണ͕͞ͳ͍ݶΓ
    WebUSBʹѲΒͤΔࣄ͕Ͱ͖ͳ͍
    ͦΕ΄͍͠ ΍ΒΜ

    View Slide

  53. ·ͱΊ
    WebUSB͸ϒϥ΢βͱUSBσόΠε͕
    ௚઀ձ࿩͢Δ͜ͱΛՄೳʹ͢ΔAPIͰ͋Δ
    JavaScriptͰυϥΠόΛॻ͚͹
    ͲΜͳUSBσόΠεͰ΋ಈ͔͢͜ͱ͕Ͱ͖Δ
    Ͱ΋ϦϞʔτ͔Β߱ͬͯདྷͨ
    ಘମͷ஌Εͳ͍υϥΠό͸৴༻Ͱ͖Δͷ͔

    View Slide

  54. σόοά෩ܠ
    ͏·͘௨৴Ͱ͖ͳ͍࣌͸WiresharkͰUSBΛݟΑ͏

    View Slide