首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用DOMException API导入pem格式的公共RSA密钥时WebCrypto

使用DOMException API导入pem格式的公共RSA密钥时WebCrypto
EN

Stack Overflow用户
提问于 2022-04-24 00:06:15
回答 1查看 194关注 0票数 1

我可能忽略了一些安全问题,这使得浏览器无法将公钥文件导入到CryptoKey对象中,但我看不到文档中提到的任何内容。浏览器生成没有附加消息的"DOMException“。

我使用一个助手库"OpenCrypto“来简化密钥管理过程,但是当我直接使用WebCrypto API时(这促使我使用了一个助手库,以确保不仅仅是我误用了WebCrypto),我也收到了同样的错误。

代码语言:javascript
复制
async importKey() {
    try {
        let pem = `-----BEGIN PUBLIC KEY-----
        MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq3GvsQ5+vT+lkuGb7PP6
        slV5mNyOAQo5rhInuDMFkyJOnwIDLzOQ7tLe4ApDPt2CmuRG+qpMaul+IYPBk6Ts
        9YPdvvVh5lohiDRN7ny3Sd5uwUy4Ea/NkY62lui4zDFnUDMH8pAUcJWQW4zKloRI
        k2EsXR5A5dqOq4wv2+I76Ax9lK2qYkQBZ8ZqeePPMYU1N0lETzCgDW/FqQEk6m81
        2c8LnF2bhnrjFJ2k0lTDVx4TwvEUOEg6TbFah+PNe8CFN/cJsHMxlr4StV6nwpZu
        n62YSXo9KskLmSRNhGKUS+oNEzTeLRyNfpZb3WQFOjqlgqJFW1xp1KfEdqFk+37z
        HwIDAQAB
        -----END PUBLIC KEY-----`;

        let key = await this.#crypt.pemPublicToCrypto(pem, { name: 'RSA-PSS', usages: ['verify'], isExtractable: true });
    } catch (error) {
        console.log(`Error importing key`, error);
    }
}

公钥是使用Node的WebCrypto API (crypto.webcrypto)实现生成的,然后我简单地复制并粘贴到这个函数中来尝试它。在节点导入中运行此函数很好。在浏览器中运行它将生成DOMException。

以下是OpenCrypto源代码中的相关代码:

代码语言:javascript
复制
const cryptoLib = window.crypto || window.msCrypto
const cryptoApi = cryptoLib.subtle || cryptoLib.webkitSubtle

//... other code removed for clarity


  /**
   * Method that converts asymmetric public key from PEM to CryptoKey format
   * @param {String} publicKey default: "undefined"
   * @param {Object} options default: depends on algorithm below
   * -- ECDH: { name: 'ECDH', usages: [], isExtractable: true }
   * -- ECDSA: { name: 'ECDSA', usages: ['verify'], isExtractable: true }
   * -- RSA-OAEP: { name: 'RSA-OAEP', hash: { name: 'SHA-512' }, usages: ['encrypt', 'wrapKey'], isExtractable: true }
   * -- RSA-PSS: { name: 'RSA-PSS', hash: { name: 'SHA-512' }, usages: ['verify'], isExtractable: true }
   */
  pemPublicToCrypto (pem, options) {
    const self = this

    if (typeof options === 'undefined') {
      options = {}
    }

    options.isExtractable = (typeof options.isExtractable !== 'undefined') ? options.isExtractable : true

    return new Promise((resolve, reject) => {
      if (typeof pem !== 'string') {
        throw new TypeError('Expected input of pem to be a String')
      }

      if (typeof options.isExtractable !== 'boolean') {
        throw new TypeError('Expected input of options.isExtractable to be a Boolean')
      }

      pem = pem.replace('-----BEGIN PUBLIC KEY-----', '')
      pem = pem.replace('-----END PUBLIC KEY-----', '')

      const b64 = self.removeLines(pem)
      const arrayBuffer = self.base64ToArrayBuffer(b64)
      const hex = self.arrayBufferToHexString(arrayBuffer)
      const keyOptions = {}

      if (hex.includes(EC_OID)) {
        options.name = (typeof options.name !== 'undefined') ? options.name : 'ECDH'

        if (typeof options.name !== 'string') {
          throw new TypeError('Expected input of options.name to be a String')
        }

        let curve = null
        if (hex.includes(P256_OID)) {
          curve = 'P-256'
        } else if (hex.includes(P384_OID)) {
          curve = 'P-384'
        } else if (hex.includes(P521_OID)) {
          curve = 'P-521'
        }

        if (options.name === 'ECDH') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : []
        } else if (options.name === 'ECDSA') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : ['verify']
        } else {
          throw new TypeError('Expected input of options.name is not a valid algorithm name')
        }

        if (typeof options.usages !== 'object') {
          throw new TypeError('Expected input of options.usages to be an Array')
        }

        keyOptions.name = options.name
        keyOptions.namedCurve = curve
      } else if (hex.includes(RSA_OID)) {
        options.name = (typeof options.name !== 'undefined') ? options.name : 'RSA-OAEP'
        options.hash = (typeof options.hash !== 'undefined') ? options.hash : 'SHA-512'

        if (typeof options.name !== 'string') {
          throw new TypeError('Expected input of options.name to be a String')
        }

        if (typeof options.hash !== 'string') {
          throw new TypeError('Expected input of options.hash to be a String')
        }

        if (options.name === 'RSA-OAEP') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : ['encrypt', 'wrapKey']
        } else if (options.name === 'RSA-PSS') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : ['verify']
        } else {
          throw new TypeError('Expected input of options.name is not a valid algorithm name')
        }

        if (typeof options.usages !== 'object') {
          throw new TypeError('Expected input of options.usages to be an Array')
        }

        keyOptions.name = options.name
        keyOptions.hash = {}
        keyOptions.hash.name = options.hash
      } else {
        throw new TypeError('Expected input of pem is not a valid public key')
      }

      cryptoApi.importKey(
        'spki',
        arrayBuffer,
        keyOptions,
        options.isExtractable,
        options.usages
      ).then(importedPublicKey => {
        resolve(importedPublicKey)
      }).catch(err => {
        reject(err)
      })
    })
  }

错误由SubtleCrypto.importKey()函数引发。

我试过ECDSA和RSA-PSS密钥,以防两者不兼容,但两者都会产生相同的错误。

如果能提供任何帮助,我将不胜感激。

谢谢

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-04-24 11:06:00

这个问题仅仅是由键的缩进引起的。

这导致了从PEM到DER的转换过程中的损坏:在pemPublicToCrypto()中,行const b64 = self.removeLines(pem)中删除了行中断。但是,这并不会删除可能的空格或制表符,因此在随后的ArrayBufferconst arrayBuffer = self.base64ToArrayBuffer(b64)的转换过程中,这些空格或制表符包含在数据中,这会破坏数据。

因此,解决方案是省略缩进或删除缩进,例如使用pem.replace(/(\s)[ \t]+/g, '$1')

代码语言:javascript
复制
async function importKey() {
  var c = new OpenCrypto();
  try {  

    // 
    // Without indentation: Works
    //
    var pem = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq3GvsQ5+vT+lkuGb7PP6
slV5mNyOAQo5rhInuDMFkyJOnwIDLzOQ7tLe4ApDPt2CmuRG+qpMaul+IYPBk6Ts
9YPdvvVh5lohiDRN7ny3Sd5uwUy4Ea/NkY62lui4zDFnUDMH8pAUcJWQW4zKloRI
k2EsXR5A5dqOq4wv2+I76Ax9lK2qYkQBZ8ZqeePPMYU1N0lETzCgDW/FqQEk6m81
2c8LnF2bhnrjFJ2k0lTDVx4TwvEUOEg6TbFah+PNe8CFN/cJsHMxlr4StV6nwpZu
n62YSXo9KskLmSRNhGKUS+oNEzTeLRyNfpZb3WQFOjqlgqJFW1xp1KfEdqFk+37z
HwIDAQAB
-----END PUBLIC KEY-----`;
    console.log("Key without indentation:") 
    var key = await c.pemPublicToCrypto(pem, { name: 'RSA-PSS', usages: ['verify'], isExtractable: true });
    console.log(key);  
    
    // 
    // With indentation and space/tab removal: Works
    //
    var pem = `-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq3GvsQ5+vT+lkuGb7PP6
    slV5mNyOAQo5rhInuDMFkyJOnwIDLzOQ7tLe4ApDPt2CmuRG+qpMaul+IYPBk6Ts
    9YPdvvVh5lohiDRN7ny3Sd5uwUy4Ea/NkY62lui4zDFnUDMH8pAUcJWQW4zKloRI
    k2EsXR5A5dqOq4wv2+I76Ax9lK2qYkQBZ8ZqeePPMYU1N0lETzCgDW/FqQEk6m81
    2c8LnF2bhnrjFJ2k0lTDVx4TwvEUOEg6TbFah+PNe8CFN/cJsHMxlr4StV6nwpZu
    n62YSXo9KskLmSRNhGKUS+oNEzTeLRyNfpZb3WQFOjqlgqJFW1xp1KfEdqFk+37z
    HwIDAQAB
    -----END PUBLIC KEY-----`;
    console.log("Key with indentation and space/tab removal:") 
    var key = await c.pemPublicToCrypto(pem.replace(/(\s)[ \t]+/g, '$1'), { name: 'RSA-PSS', usages: ['verify'], isExtractable: true });
    console.log(key);  
    
    //
    // With indentation: Doesn't work
    //
    var pem = `-----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq3GvsQ5+vT+lkuGb7PP6
    slV5mNyOAQo5rhInuDMFkyJOnwIDLzOQ7tLe4ApDPt2CmuRG+qpMaul+IYPBk6Ts
    9YPdvvVh5lohiDRN7ny3Sd5uwUy4Ea/NkY62lui4zDFnUDMH8pAUcJWQW4zKloRI
    k2EsXR5A5dqOq4wv2+I76Ax9lK2qYkQBZ8ZqeePPMYU1N0lETzCgDW/FqQEk6m81
    2c8LnF2bhnrjFJ2k0lTDVx4TwvEUOEg6TbFah+PNe8CFN/cJsHMxlr4StV6nwpZu
    n62YSXo9KskLmSRNhGKUS+oNEzTeLRyNfpZb3WQFOjqlgqJFW1xp1KfEdqFk+37z
    HwIDAQAB
    -----END PUBLIC KEY-----`;

    console.log("Key with indentation:");  
    var key = await c.pemPublicToCrypto(pem, { name: 'RSA-PSS', usages: ['verify'], isExtractable: true });
    console.log(key);  
    
  } catch (error) {
    console.log(`Error importing key`, error);
  }
}

// --------------------------------------------------------------------------------------------------

/**
 *
 * Copyright (c) 2016 SafeBash
 * Cryptography consultant: Andrew Kozlik, Ph.D.
 *
 */

/**
 * MIT License
 *
 * Copyright (c) 2016 SafeBash
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
const cryptoLib = window.crypto || window.msCrypto
const cryptoApi = cryptoLib.subtle || cryptoLib.webkitSubtle
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const lookup = new Uint8Array(256)

const RSA_OID = '06092a864886f70d010101'
const EC_OID = '06072a8648ce3d0201'
const P256_OID = '06082a8648ce3d030107'
const P384_OID = '06052b81040022'
const P521_OID = '06052b81040023'

class OpenCrypto {
  constructor () {
    for (let i = 0; i < chars.length; i++) {
      lookup[chars.charCodeAt(i)] = i
    }
  }

  /**
   * BEGIN
   * base64-arraybuffer
   * GitHub @niklasvh
   * Copyright (c) 2012 Niklas von Hertzen
   * MIT License
   */
  decodeAb (base64) {
    const len = base64.length
    let bufferLength = base64.length * 0.75
    let p = 0
    let encoded1
    let encoded2
    let encoded3
    let encoded4

    if (base64[base64.length - 1] === '=') {
      bufferLength--
      if (base64[base64.length - 2] === '=') {
        bufferLength--
      }
    }

    const arrayBuffer = new ArrayBuffer(bufferLength)
    let bytes = new Uint8Array(arrayBuffer)

    for (let i = 0; i < len; i += 4) {
      encoded1 = lookup[base64.charCodeAt(i)]
      encoded2 = lookup[base64.charCodeAt(i + 1)]
      encoded3 = lookup[base64.charCodeAt(i + 2)]
      encoded4 = lookup[base64.charCodeAt(i + 3)]

      bytes[p++] = (encoded1 << 2) | (encoded2 >> 4)
      bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2)
      bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63)
    }

    return arrayBuffer
  }
  /**
   * END
   * base64-arraybuffer
   */

  /**
   * Method for encoding ArrayBuffer to hexadecimal String
   */
  arrayBufferToHexString (arrayBuffer) {
    if (typeof arrayBuffer !== 'object') {
      throw new TypeError('Expected input of arrayBuffer to be an ArrayBuffer Object')
    }

    const byteArray = new Uint8Array(arrayBuffer)
    let hexString = ''
    let nextHexByte

    for (let i = 0; i < byteArray.byteLength; i++) {
      nextHexByte = byteArray[i].toString(16)

      if (nextHexByte.length < 2) {
        nextHexByte = '0' + nextHexByte
      }

      hexString += nextHexByte
    }

    return hexString
  }

  /**
   * Method for decoding base64 String to ArrayBuffer
   */
  base64ToArrayBuffer (b64) {
    if (typeof b64 !== 'string') {
      throw new TypeError('Expected input of b64 to be a Base64 String')
    }

    return this.decodeAb(b64)
  }

  /**
   * Method that removes lines from PEM encoded key
   */
  removeLines (str) {
    return str.replace(/\r?\n|\r/g, '')
  }

  /**
   * Method that converts asymmetric public key from PEM to CryptoKey format
   * @param {String} publicKey default: "undefined"
   * @param {Object} options default: depends on algorithm below
   * -- ECDH: { name: 'ECDH', usages: [], isExtractable: true }
   * -- ECDSA: { name: 'ECDSA', usages: ['verify'], isExtractable: true }
   * -- RSA-OAEP: { name: 'RSA-OAEP', hash: { name: 'SHA-512' }, usages: ['encrypt', 'wrapKey'], isExtractable: true }
   * -- RSA-PSS: { name: 'RSA-PSS', hash: { name: 'SHA-512' }, usages: ['verify'], isExtractable: true }
   */
  pemPublicToCrypto (pem, options) {
    const self = this

    if (typeof options === 'undefined') {
      options = {}
    }

    options.isExtractable = (typeof options.isExtractable !== 'undefined') ? options.isExtractable : true

    return new Promise((resolve, reject) => {
      if (typeof pem !== 'string') {
        throw new TypeError('Expected input of pem to be a String')
      }

      if (typeof options.isExtractable !== 'boolean') {
        throw new TypeError('Expected input of options.isExtractable to be a Boolean')
      }

      pem = pem.replace('-----BEGIN PUBLIC KEY-----', '')
      pem = pem.replace('-----END PUBLIC KEY-----', '')

      const b64 = self.removeLines(pem)
      const arrayBuffer = self.base64ToArrayBuffer(b64)
      const hex = self.arrayBufferToHexString(arrayBuffer)
      const keyOptions = {}

      if (hex.includes(EC_OID)) {
        options.name = (typeof options.name !== 'undefined') ? options.name : 'ECDH'

        if (typeof options.name !== 'string') {
          throw new TypeError('Expected input of options.name to be a String')
        }

        let curve = null
        if (hex.includes(P256_OID)) {
          curve = 'P-256'
        } else if (hex.includes(P384_OID)) {
          curve = 'P-384'
        } else if (hex.includes(P521_OID)) {
          curve = 'P-521'
        }

        if (options.name === 'ECDH') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : []
        } else if (options.name === 'ECDSA') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : ['verify']
        } else {
          throw new TypeError('Expected input of options.name is not a valid algorithm name')
        }

        if (typeof options.usages !== 'object') {
          throw new TypeError('Expected input of options.usages to be an Array')
        }

        keyOptions.name = options.name
        keyOptions.namedCurve = curve
      } else if (hex.includes(RSA_OID)) {
        options.name = (typeof options.name !== 'undefined') ? options.name : 'RSA-OAEP'
        options.hash = (typeof options.hash !== 'undefined') ? options.hash : 'SHA-512'

        if (typeof options.name !== 'string') {
          throw new TypeError('Expected input of options.name to be a String')
        }

        if (typeof options.hash !== 'string') {
          throw new TypeError('Expected input of options.hash to be a String')
        }

        if (options.name === 'RSA-OAEP') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : ['encrypt', 'wrapKey']
        } else if (options.name === 'RSA-PSS') {
          options.usages = (typeof options.usages !== 'undefined') ? options.usages : ['verify']
        } else {
          throw new TypeError('Expected input of options.name is not a valid algorithm name')
        }

        if (typeof options.usages !== 'object') {
          throw new TypeError('Expected input of options.usages to be an Array')
        }

        keyOptions.name = options.name
        keyOptions.hash = {}
        keyOptions.hash.name = options.hash
      } else {
        throw new TypeError('Expected input of pem is not a valid public key')
      }

      cryptoApi.importKey(
        'spki',
        arrayBuffer,
        keyOptions,
        options.isExtractable,
        options.usages
      ).then(importedPublicKey => {
        resolve(importedPublicKey)
      }).catch(err => {
        reject(err)
      })
    })
  }
}

// --------------------------------------------------------------------------------------------------
  
(async () => {
    await importKey();
})();

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71984523

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档