ecdsa-sig-formatter.js
1 'use strict'; 2 3 var Buffer = require('safe-buffer').Buffer; 4 5 var getParamBytesForAlg = require('./param-bytes-for-alg'); 6 7 var MAX_OCTET = 0x80, 8 CLASS_UNIVERSAL = 0, 9 PRIMITIVE_BIT = 0x20, 10 TAG_SEQ = 0x10, 11 TAG_INT = 0x02, 12 ENCODED_TAG_SEQ = (TAG_SEQ | PRIMITIVE_BIT) | (CLASS_UNIVERSAL << 6), 13 ENCODED_TAG_INT = TAG_INT | (CLASS_UNIVERSAL << 6); 14 15 function base64Url(base64) { 16 return base64 17 .replace(/=/g, '') 18 .replace(/\+/g, '-') 19 .replace(/\//g, '_'); 20 } 21 22 function signatureAsBuffer(signature) { 23 if (Buffer.isBuffer(signature)) { 24 return signature; 25 } else if ('string' === typeof signature) { 26 return Buffer.from(signature, 'base64'); 27 } 28 29 throw new TypeError('ECDSA signature must be a Base64 string or a Buffer'); 30 } 31 32 function derToJose(signature, alg) { 33 signature = signatureAsBuffer(signature); 34 var paramBytes = getParamBytesForAlg(alg); 35 36 // the DER encoded param should at most be the param size, plus a padding 37 // zero, since due to being a signed integer 38 var maxEncodedParamLength = paramBytes + 1; 39 40 var inputLength = signature.length; 41 42 var offset = 0; 43 if (signature[offset++] !== ENCODED_TAG_SEQ) { 44 throw new Error('Could not find expected "seq"'); 45 } 46 47 var seqLength = signature[offset++]; 48 if (seqLength === (MAX_OCTET | 1)) { 49 seqLength = signature[offset++]; 50 } 51 52 if (inputLength - offset < seqLength) { 53 throw new Error('"seq" specified length of "' + seqLength + '", only "' + (inputLength - offset) + '" remaining'); 54 } 55 56 if (signature[offset++] !== ENCODED_TAG_INT) { 57 throw new Error('Could not find expected "int" for "r"'); 58 } 59 60 var rLength = signature[offset++]; 61 62 if (inputLength - offset - 2 < rLength) { 63 throw new Error('"r" specified length of "' + rLength + '", only "' + (inputLength - offset - 2) + '" available'); 64 } 65 66 if (maxEncodedParamLength < rLength) { 67 throw new Error('"r" specified length of "' + rLength + '", max of "' + maxEncodedParamLength + '" is acceptable'); 68 } 69 70 var rOffset = offset; 71 offset += rLength; 72 73 if (signature[offset++] !== ENCODED_TAG_INT) { 74 throw new Error('Could not find expected "int" for "s"'); 75 } 76 77 var sLength = signature[offset++]; 78 79 if (inputLength - offset !== sLength) { 80 throw new Error('"s" specified length of "' + sLength + '", expected "' + (inputLength - offset) + '"'); 81 } 82 83 if (maxEncodedParamLength < sLength) { 84 throw new Error('"s" specified length of "' + sLength + '", max of "' + maxEncodedParamLength + '" is acceptable'); 85 } 86 87 var sOffset = offset; 88 offset += sLength; 89 90 if (offset !== inputLength) { 91 throw new Error('Expected to consume entire buffer, but "' + (inputLength - offset) + '" bytes remain'); 92 } 93 94 var rPadding = paramBytes - rLength, 95 sPadding = paramBytes - sLength; 96 97 var dst = Buffer.allocUnsafe(rPadding + rLength + sPadding + sLength); 98 99 for (offset = 0; offset < rPadding; ++offset) { 100 dst[offset] = 0; 101 } 102 signature.copy(dst, offset, rOffset + Math.max(-rPadding, 0), rOffset + rLength); 103 104 offset = paramBytes; 105 106 for (var o = offset; offset < o + sPadding; ++offset) { 107 dst[offset] = 0; 108 } 109 signature.copy(dst, offset, sOffset + Math.max(-sPadding, 0), sOffset + sLength); 110 111 dst = dst.toString('base64'); 112 dst = base64Url(dst); 113 114 return dst; 115 } 116 117 function countPadding(buf, start, stop) { 118 var padding = 0; 119 while (start + padding < stop && buf[start + padding] === 0) { 120 ++padding; 121 } 122 123 var needsSign = buf[start + padding] >= MAX_OCTET; 124 if (needsSign) { 125 --padding; 126 } 127 128 return padding; 129 } 130 131 function joseToDer(signature, alg) { 132 signature = signatureAsBuffer(signature); 133 var paramBytes = getParamBytesForAlg(alg); 134 135 var signatureBytes = signature.length; 136 if (signatureBytes !== paramBytes * 2) { 137 throw new TypeError('"' + alg + '" signatures must be "' + paramBytes * 2 + '" bytes, saw "' + signatureBytes + '"'); 138 } 139 140 var rPadding = countPadding(signature, 0, paramBytes); 141 var sPadding = countPadding(signature, paramBytes, signature.length); 142 var rLength = paramBytes - rPadding; 143 var sLength = paramBytes - sPadding; 144 145 var rsBytes = 1 + 1 + rLength + 1 + 1 + sLength; 146 147 var shortLength = rsBytes < MAX_OCTET; 148 149 var dst = Buffer.allocUnsafe((shortLength ? 2 : 3) + rsBytes); 150 151 var offset = 0; 152 dst[offset++] = ENCODED_TAG_SEQ; 153 if (shortLength) { 154 // Bit 8 has value "0" 155 // bits 7-1 give the length. 156 dst[offset++] = rsBytes; 157 } else { 158 // Bit 8 of first octet has value "1" 159 // bits 7-1 give the number of additional length octets. 160 dst[offset++] = MAX_OCTET | 1; 161 // length, base 256 162 dst[offset++] = rsBytes & 0xff; 163 } 164 dst[offset++] = ENCODED_TAG_INT; 165 dst[offset++] = rLength; 166 if (rPadding < 0) { 167 dst[offset++] = 0; 168 offset += signature.copy(dst, offset, 0, paramBytes); 169 } else { 170 offset += signature.copy(dst, offset, rPadding, paramBytes); 171 } 172 dst[offset++] = ENCODED_TAG_INT; 173 dst[offset++] = sLength; 174 if (sPadding < 0) { 175 dst[offset++] = 0; 176 signature.copy(dst, offset, paramBytes); 177 } else { 178 signature.copy(dst, offset, paramBytes + sPadding); 179 } 180 181 return dst; 182 } 183 184 module.exports = { 185 derToJose: derToJose, 186 joseToDer: joseToDer 187 };