• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

opengovsg / formsg-javascript-sdk / 13652601401

04 Mar 2025 11:24AM UTC coverage: 97.495%. First build
13652601401

Pull #115

github

GitHub
Merge 8fbb6680c into 2c59c0547
Pull Request #115:

104 of 114 branches covered (91.23%)

Branch coverage included in aggregate %.

363 of 365 relevant lines covered (99.45%)

6.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.31
/src/util/crypto.ts
1
import nacl from 'tweetnacl'
4✔
2
import { decodeBase64, encodeBase64, encodeUTF8 } from 'tweetnacl-util'
4✔
3

4
import {
5
  EncryptedAttachmentContent,
6
  EncryptedContent,
7
  EncryptedFileContent,
8
  Keypair,
9
} from '../types'
10

11
/**
12
 * Helper method to generate a new keypair for encryption.
13
 * @returns The generated keypair.
14
 */
15
export const generateKeypair = (): Keypair => {
4✔
16
  const kp = nacl.box.keyPair()
68✔
17
  return {
68✔
18
    publicKey: encodeBase64(kp.publicKey),
19
    secretKey: encodeBase64(kp.secretKey),
20
  }
21
}
22

23
/**
24
 * Helper function to encrypt input with a unique keypair for each submission.
25
 * @param msg The message to encrypt
26
 * @param theirPublicKey The base-64 encoded public key
27
 * @returns The encrypted basestring
28
 * @throws error if any of the encrypt methods fail
29
 */
30
export const encryptMessage = (
4✔
31
  msg: Uint8Array,
32
  theirPublicKey: string
33
): EncryptedContent => {
34
  const submissionKeypair = generateKeypair()
28✔
35
  const nonce = nacl.randomBytes(24)
28✔
36
  const encrypted = encodeBase64(
28✔
37
    nacl.box(
38
      msg,
39
      nonce,
40
      decodeBase64(theirPublicKey),
41
      decodeBase64(submissionKeypair.secretKey)
42
    )
43
  )
44
  return `${submissionKeypair.publicKey};${encodeBase64(nonce)}:${encrypted}`
28✔
45
}
46

47
/**
48
 * Helper method to decrypt an encrypted submission.
49
 * @param formPrivateKey base64
50
 * @param encryptedContent encrypted string encoded in base64
51
 * @return The decrypted content, or null if decryption failed.
52
 */
53
export const decryptContent = (
4✔
54
  formPrivateKey: string,
55
  encryptedContent: EncryptedContent
56
): Uint8Array | null => {
57
  try {
32✔
58
    const [submissionPublicKey, nonceEncrypted] = encryptedContent.split(';')
32✔
59
    const [nonce, encrypted] = nonceEncrypted.split(':').map(decodeBase64)
32✔
60
    return nacl.box.open(
30✔
61
      encrypted,
62
      nonce,
63
      decodeBase64(submissionPublicKey),
64
      decodeBase64(formPrivateKey)
65
    )
66
  } catch (err) {
67
    return null
4✔
68
  }
69
}
70

71
/**
72
 * Helper method to verify a signed message.
73
 * @param msg the message to verify
74
 * @param publicKey the public key to authenticate the signed message with
75
 * @returns the signed message if successful, else an error will be thrown
76
 * @throws {Error} if the message cannot be verified
77
 */
78
export const verifySignedMessage = (
4✔
79
  msg: Uint8Array,
80
  publicKey: string
81
): Record<string, any> => {
82
  const openedMessage = nacl.sign.open(msg, decodeBase64(publicKey))
1✔
83
  if (!openedMessage)
1!
84
    throw new Error('Failed to open signed message with given public key')
×
85
  return JSON.parse(encodeUTF8(openedMessage))
1✔
86
}
87

88
/**
89
 * Helper method to check if all the field IDs given are within the filenames
90
 * @param fieldIds the list of fieldIds to check
91
 * @param filenames the filenames that should contain the fields
92
 * @returns boolean indicating whether the fields are valid
93
 */
94
export const areAttachmentFieldIdsValid = (
4✔
95
  fieldIds: string[],
96
  filenames: Record<string, string>
97
): boolean => {
98
  return fieldIds.every((fieldId) => filenames[fieldId])
9✔
99
}
100

101
/**
102
 * Converts an encrypted attachment to encrypted file content
103
 * @param encryptedAttachment The encrypted attachment
104
 * @returns EncryptedFileContent The encrypted file content
105
 */
106
export const convertEncryptedAttachmentToFileContent = (
4✔
107
  encryptedAttachment: EncryptedAttachmentContent
108
): EncryptedFileContent => ({
3✔
109
  submissionPublicKey: encryptedAttachment.encryptedFile.submissionPublicKey,
110
  nonce: encryptedAttachment.encryptedFile.nonce,
111
  binary: decodeBase64(encryptedAttachment.encryptedFile.binary),
112
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc