entry.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.toProposedEntry = toProposedEntry;
  4. /*
  5. Copyright 2023 The Sigstore Authors.
  6. Licensed under the Apache License, Version 2.0 (the "License");
  7. you may not use this file except in compliance with the License.
  8. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing, software
  11. distributed under the License is distributed on an "AS IS" BASIS,
  12. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. See the License for the specific language governing permissions and
  14. limitations under the License.
  15. */
  16. const bundle_1 = require("@sigstore/bundle");
  17. const util_1 = require("../../util");
  18. const SHA256_ALGORITHM = 'sha256';
  19. function toProposedEntry(content, publicKey,
  20. // TODO: Remove this parameter once have completely switched to 'dsse' entries
  21. entryType = 'dsse') {
  22. switch (content.$case) {
  23. case 'dsseEnvelope':
  24. // TODO: Remove this conditional once have completely ditched "intoto" entries
  25. if (entryType === 'intoto') {
  26. return toProposedIntotoEntry(content.dsseEnvelope, publicKey);
  27. }
  28. return toProposedDSSEEntry(content.dsseEnvelope, publicKey);
  29. case 'messageSignature':
  30. return toProposedHashedRekordEntry(content.messageSignature, publicKey);
  31. }
  32. }
  33. // Returns a properly formatted Rekor "hashedrekord" entry for the given digest
  34. // and signature
  35. function toProposedHashedRekordEntry(messageSignature, publicKey) {
  36. const hexDigest = messageSignature.messageDigest.digest.toString('hex');
  37. const b64Signature = messageSignature.signature.toString('base64');
  38. const b64Key = util_1.encoding.base64Encode(publicKey);
  39. return {
  40. apiVersion: '0.0.1',
  41. kind: 'hashedrekord',
  42. spec: {
  43. data: {
  44. hash: {
  45. algorithm: SHA256_ALGORITHM,
  46. value: hexDigest,
  47. },
  48. },
  49. signature: {
  50. content: b64Signature,
  51. publicKey: {
  52. content: b64Key,
  53. },
  54. },
  55. },
  56. };
  57. }
  58. // Returns a properly formatted Rekor "dsse" entry for the given DSSE envelope
  59. // and signature
  60. function toProposedDSSEEntry(envelope, publicKey) {
  61. const envelopeJSON = JSON.stringify((0, bundle_1.envelopeToJSON)(envelope));
  62. const encodedKey = util_1.encoding.base64Encode(publicKey);
  63. return {
  64. apiVersion: '0.0.1',
  65. kind: 'dsse',
  66. spec: {
  67. proposedContent: {
  68. envelope: envelopeJSON,
  69. verifiers: [encodedKey],
  70. },
  71. },
  72. };
  73. }
  74. // Returns a properly formatted Rekor "intoto" entry for the given DSSE
  75. // envelope and signature
  76. function toProposedIntotoEntry(envelope, publicKey) {
  77. // Calculate the value for the payloadHash field in the Rekor entry
  78. const payloadHash = util_1.crypto
  79. .digest(SHA256_ALGORITHM, envelope.payload)
  80. .toString('hex');
  81. // Calculate the value for the hash field in the Rekor entry
  82. const envelopeHash = calculateDSSEHash(envelope, publicKey);
  83. // Collect values for re-creating the DSSE envelope.
  84. // Double-encode payload and signature cause that's what Rekor expects
  85. const payload = util_1.encoding.base64Encode(envelope.payload.toString('base64'));
  86. const sig = util_1.encoding.base64Encode(envelope.signatures[0].sig.toString('base64'));
  87. const keyid = envelope.signatures[0].keyid;
  88. const encodedKey = util_1.encoding.base64Encode(publicKey);
  89. // Create the envelope portion of the entry. Note the inclusion of the
  90. // publicKey in the signature struct is not a standard part of a DSSE
  91. // envelope, but is required by Rekor.
  92. const dsse = {
  93. payloadType: envelope.payloadType,
  94. payload: payload,
  95. signatures: [{ sig, publicKey: encodedKey }],
  96. };
  97. // If the keyid is an empty string, Rekor seems to remove it altogether. We
  98. // need to do the same here so that we can properly recreate the entry for
  99. // verification.
  100. if (keyid.length > 0) {
  101. dsse.signatures[0].keyid = keyid;
  102. }
  103. return {
  104. apiVersion: '0.0.2',
  105. kind: 'intoto',
  106. spec: {
  107. content: {
  108. envelope: dsse,
  109. hash: { algorithm: SHA256_ALGORITHM, value: envelopeHash },
  110. payloadHash: { algorithm: SHA256_ALGORITHM, value: payloadHash },
  111. },
  112. },
  113. };
  114. }
  115. // Calculates the hash of a DSSE envelope for inclusion in a Rekor entry.
  116. // There is no standard way to do this, so the scheme we're using as as
  117. // follows:
  118. // * payload is base64 encoded
  119. // * signature is base64 encoded (only the first signature is used)
  120. // * keyid is included ONLY if it is NOT an empty string
  121. // * The resulting JSON is canonicalized and hashed to a hex string
  122. function calculateDSSEHash(envelope, publicKey) {
  123. const dsse = {
  124. payloadType: envelope.payloadType,
  125. payload: envelope.payload.toString('base64'),
  126. signatures: [
  127. { sig: envelope.signatures[0].sig.toString('base64'), publicKey },
  128. ],
  129. };
  130. // If the keyid is an empty string, Rekor seems to remove it altogether.
  131. if (envelope.signatures[0].keyid.length > 0) {
  132. dsse.signatures[0].keyid = envelope.signatures[0].keyid;
  133. }
  134. return util_1.crypto
  135. .digest(SHA256_ALGORITHM, util_1.json.canonicalize(dsse))
  136. .toString('hex');
  137. }