types/rsa-sha256.js

  1. 'use strict'
  2. /**
  3. * @module types
  4. */
  5. const Rsa = require('../crypto/rsa')
  6. const pem = require('../util/pem')
  7. const BaseSha256 = require('./base-sha256')
  8. const MissingDataError = require('../errors/missing-data-error')
  9. const ValidationError = require('../errors/validation-error')
  10. const Asn1RsaFingerprintContents = require('../schemas/fingerprint').RsaFingerprintContents
  11. // Instantiate RSA signer with standard settings
  12. const rsa = new Rsa()
  13. /**
  14. * RSA-SHA-256: RSA signature condition using SHA-256.
  15. *
  16. * This RSA condition uses RSA-PSS padding with SHA-256. The salt length is set
  17. * equal the digest length of 32 bytes.
  18. *
  19. * The public exponent is fixed at 65537 and the public modulus must be between
  20. * 128 (1017 bits) and 512 bytes (4096 bits) long.
  21. *
  22. * RSA-SHA-256 is assigned the type ID 3. It relies on the SHA-256 and RSA-PSS
  23. * feature suites which corresponds to a feature bitmask of 0x11.
  24. */
  25. class RsaSha256 extends BaseSha256 {
  26. constructor () {
  27. super()
  28. this.modulus = null
  29. this.signature = null
  30. }
  31. parseJson (json) {
  32. this.modulus = Buffer.from(json.modulus, 'base64')
  33. this.signature = Buffer.from(json.signature, 'base64')
  34. }
  35. /**
  36. * Produce the contents of the condition hash.
  37. *
  38. * This function is called internally by the `getCondition` method.
  39. *
  40. * @return {Buffer} Encoded contents of fingerprint hash.
  41. *
  42. * @private
  43. */
  44. getFingerprintContents () {
  45. if (!this.modulus) {
  46. throw new MissingDataError('Requires modulus')
  47. }
  48. return Asn1RsaFingerprintContents.encode({
  49. modulus: this.modulus
  50. })
  51. }
  52. getAsn1JsonPayload () {
  53. return {
  54. modulus: this.modulus,
  55. signature: this.signature
  56. }
  57. }
  58. /**
  59. * Set the public modulus.
  60. *
  61. * This is the modulus of the RSA public key. It has to be provided as a raw
  62. * buffer with no leading zeros.
  63. *
  64. * @param {Buffer} modulus Public RSA modulus
  65. */
  66. setPublicModulus (modulus) {
  67. if (!Buffer.isBuffer(modulus)) {
  68. throw new TypeError('Modulus must be a buffer, was: ' + modulus)
  69. }
  70. if (modulus[0] === 0) {
  71. throw new Error('Modulus may not contain leading zeros')
  72. }
  73. if (modulus.length > 512 || modulus.length < 128) {
  74. throw new Error('Modulus must be between 128 bytes (1017 bits) and ' +
  75. '512 bytes (4096 bits), was: ' + modulus.length + ' bytes')
  76. }
  77. this.modulus = modulus
  78. }
  79. /**
  80. * Set the signature manually.
  81. *
  82. * The signature must be a valid RSA-PSS siganture.
  83. *
  84. * @param {Buffer} signature RSA signature.
  85. */
  86. setSignature (signature) {
  87. if (!Buffer.isBuffer(signature)) {
  88. throw new TypeError('Signature must be a buffer, was: ' + signature)
  89. }
  90. this.signature = signature
  91. }
  92. /**
  93. * Sign the message.
  94. *
  95. * This method will take the provided message and create a signature using the
  96. * provided RSA private key. The resulting signature is stored in the
  97. * fulfillment.
  98. *
  99. * The key should be provided as a PEM encoded private key string.
  100. *
  101. * The message is padded using RSA-PSS with SHA256.
  102. *
  103. * @param {Buffer} message Message to sign.
  104. * @param {String} privateKey RSA private key
  105. */
  106. sign (message, privateKey) {
  107. if (!this.modulus) {
  108. this.setPublicModulus(pem.modulusFromPrivateKey(privateKey))
  109. }
  110. this.signature = rsa.sign(privateKey, message)
  111. }
  112. /**
  113. * Calculate the cost of fulfilling this condition.
  114. *
  115. * The cost of the RSA condition is the size of the modulus squared, divided
  116. * by 64.
  117. *
  118. * @return {Number} Expected maximum cost to fulfill this condition
  119. * @private
  120. */
  121. calculateCost () {
  122. if (!this.modulus) {
  123. throw new MissingDataError('Requires a public modulus')
  124. }
  125. return Math.pow(rsa.getModulusBitLength(this.modulus), 2) >>> RsaSha256.COST_RIGHT_SHIFT
  126. }
  127. /**
  128. * Verify the signature of this RSA fulfillment.
  129. *
  130. * The signature of this RSA fulfillment is verified against the provided
  131. * message and the condition's public modulus.
  132. *
  133. * @param {Buffer} message Message to verify.
  134. * @return {Boolean} Whether this fulfillment is valid.
  135. */
  136. validate (message) {
  137. if (!Buffer.isBuffer(message)) {
  138. throw new Error('Message must be provided as a Buffer, was: ' + message)
  139. }
  140. const pssResult = rsa.verify(this.modulus, message, this.signature)
  141. if (!pssResult) {
  142. throw new ValidationError('Invalid RSA signature')
  143. }
  144. return true
  145. }
  146. }
  147. RsaSha256.TYPE_ID = 3
  148. RsaSha256.TYPE_NAME = 'rsa-sha-256'
  149. RsaSha256.TYPE_ASN1_CONDITION = 'rsaSha256Condition'
  150. RsaSha256.TYPE_ASN1_FULFILLMENT = 'rsaSha256Fulfillment'
  151. RsaSha256.TYPE_CATEGORY = 'simple'
  152. RsaSha256.COST_RIGHT_SHIFT = 6 // 2^6 = 64
  153. module.exports = RsaSha256