1 import { CryptoProxy } from '@proton/crypto';
2 import { KT_SKL_SIGNING_CONTEXT } from '@proton/key-transparency/lib';
3 import isTruthy from '@proton/utils/isTruthy';
5 import { getIsAddressDisabled } from '../helpers/address';
7 ActiveAddressKeysByVersion,
11 KeyMigrationKTVerifier,
12 KeyTransparencyVerify,
15 } from '../interfaces';
16 import type { SimpleMap } from '../interfaces/utils';
17 import { getActiveAddressKeys, getNormalizedActiveAddressKeys } from './getActiveKeys';
18 import type { PrimaryAddressKeys} from './getPrimaryKey';
19 import { getPrimaryAddressKeysForSigningByVersion } from './getPrimaryKey';
21 export const getSignedKeyListSignature = async (data: string, signingKeys: PrimaryAddressKeys, date?: Date) => {
22 const signature = await CryptoProxy.signMessage({
24 stripTrailingSpaces: true,
27 context: KT_SKL_SIGNING_CONTEXT,
33 export type OnSKLPublishSuccess = () => Promise<void>;
36 * Generate the signed key list data and verify it for later commit to Key Transparency.
37 * The SKL is only considered in the later commit call if the returned OnSKLPublishSuccess closure
38 * has been called beforehand.
40 export const getSignedKeyListWithDeferredPublish = async (
41 keys: ActiveAddressKeysByVersion,
43 keyTransparencyVerify: KeyTransparencyVerify
44 ): Promise<[SignedKeyList, OnSKLPublishSuccess]> => {
45 // the v6 primary key (if present) must come after the v4 one
46 const list = [...keys.v4, ...keys.v6].sort((a, b) => b.primary - a.primary);
47 const transformedKeys = (
49 list.map(async ({ privateKey, flags, primary, sha256Fingerprints, fingerprint }) => {
50 const result = await CryptoProxy.isE2EEForwardingKey({ key: privateKey });
59 Fingerprint: fingerprint,
60 SHA256Fingerprints: sha256Fingerprints,
65 const data = JSON.stringify(transformedKeys);
66 const signingKeys = getPrimaryAddressKeysForSigningByVersion(keys);
67 if (!signingKeys.length) {
68 throw new Error('Missing primary signing key');
71 // TODO: Could be filtered as well
72 const publicKeys = list.map((key) => key.publicKey);
74 const signedKeyList: SignedKeyList = {
76 Signature: await getSignedKeyListSignature(data, signingKeys),
78 const onSKLPublish = async () => {
79 if (!getIsAddressDisabled(address)) {
80 await keyTransparencyVerify(address, signedKeyList, publicKeys);
83 return [signedKeyList, onSKLPublish];
87 * Generate the signed key list data and verify it for later commit to Key Transparency
89 export const getSignedKeyList = async (
90 keys: ActiveAddressKeysByVersion,
92 keyTransparencyVerify: KeyTransparencyVerify
93 ): Promise<SignedKeyList> => {
94 const activeKeysWithoutForwarding = {
97 keys.v4.map(async (key) => {
98 const result = await CryptoProxy.isE2EEForwardingKey({ key: key.privateKey });
99 return result ? false : key;
103 v6: keys.v6, // forwarding not supported by v6 keys
106 const [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
107 activeKeysWithoutForwarding,
109 keyTransparencyVerify
111 await onSKLPublishSuccess();
112 return signedKeyList;
115 export const createSignedKeyListForMigration = async ({
118 keyMigrationKTVerifier,
119 keyTransparencyVerify,
124 decryptedKeys: DecryptedKey[];
125 keyTransparencyVerify: KeyTransparencyVerify;
126 keyMigrationKTVerifier: KeyMigrationKTVerifier;
127 }): Promise<[SignedKeyList | undefined, OnSKLPublishSuccess | undefined]> => {
128 let signedKeyList: SignedKeyList | undefined;
129 let onSKLPublishSuccess: OnSKLPublishSuccess | undefined;
130 if (!address.SignedKeyList || address.SignedKeyList.ObsolescenceToken) {
131 // Only create a new signed key list if the address does not have one already
132 // or the signed key list is obsolete.
133 await keyMigrationKTVerifier({ email: address.Email, signedKeyList: address.SignedKeyList, api });
134 const activeKeys = getNormalizedActiveAddressKeys(
136 await getActiveAddressKeys(address, address.SignedKeyList, address.Keys, decryptedKeys)
138 if (activeKeys.v4.length > 0) {
139 // v4 keys always presents, no need to check for v6 ones
140 [signedKeyList, onSKLPublishSuccess] = await getSignedKeyListWithDeferredPublish(
143 keyTransparencyVerify
147 return [signedKeyList, onSKLPublishSuccess];
150 const signedKeyListItemParser = ({ Primary, Flags, Fingerprint, SHA256Fingerprints }: any) =>
151 (Primary === 0 || Primary === 1) &&
152 typeof Flags === 'number' &&
153 typeof Fingerprint === 'string' &&
154 Array.isArray(SHA256Fingerprints) &&
155 SHA256Fingerprints.every((fingerprint) => typeof fingerprint === 'string');
157 export const getParsedSignedKeyList = (data?: string | null): SignedKeyListItem[] | undefined => {
162 const parsedData = JSON.parse(data);
163 if (!Array.isArray(parsedData)) {
166 if (!parsedData.every(signedKeyListItemParser)) {
175 export const getSignedKeyListMap = (signedKeyListData?: SignedKeyListItem[]): SimpleMap<SignedKeyListItem> => {
176 if (!signedKeyListData) {
179 return signedKeyListData.reduce<SimpleMap<SignedKeyListItem>>((acc, cur) => {
180 acc[cur.Fingerprint] = cur;