-
Notifications
You must be signed in to change notification settings - Fork 790
/
user.js
274 lines (262 loc) · 10.7 KB
/
user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/**
* @module key/User
* @private
*/
import enums from '../enums';
import util from '../util';
import { PacketList } from '../packet';
import { mergeSignatures, isDataRevoked, createSignaturePacket } from './helper';
import defaultConfig from '../config';
/**
* Class that represents an user ID or attribute packet and the relevant signatures.
* @param {UserIDPacket|UserAttributePacket} userPacket - packet containing the user info
* @param {Key} mainKey - reference to main Key object containing the primary key and subkeys that the user is associated with
*/
class User {
constructor(userPacket, mainKey) {
this.userID = userPacket.constructor.tag === enums.packet.userID ? userPacket : null;
this.userAttribute = userPacket.constructor.tag === enums.packet.userAttribute ? userPacket : null;
this.selfCertifications = [];
this.otherCertifications = [];
this.revocationSignatures = [];
this.mainKey = mainKey;
}
/**
* Transforms structured user data to packetlist
* @returns {PacketList}
*/
toPacketList() {
const packetlist = new PacketList();
packetlist.push(this.userID || this.userAttribute);
packetlist.push(...this.revocationSignatures);
packetlist.push(...this.selfCertifications);
packetlist.push(...this.otherCertifications);
return packetlist;
}
/**
* Shallow clone
* @returns {User}
*/
clone() {
const user = new User(this.userID || this.userAttribute, this.mainKey);
user.selfCertifications = [...this.selfCertifications];
user.otherCertifications = [...this.otherCertifications];
user.revocationSignatures = [...this.revocationSignatures];
return user;
}
/**
* Generate third-party certifications over this user and its primary key
* @param {Array<PrivateKey>} signingKeys - Decrypted private keys for signing
* @param {Date} [date] - Date to use as creation date of the certificate, instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<User>} New user with new certifications.
* @async
*/
async certify(signingKeys, date, config) {
const primaryKey = this.mainKey.keyPacket;
const dataToSign = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
const user = new User(dataToSign.userID || dataToSign.userAttribute, this.mainKey);
user.otherCertifications = await Promise.all(signingKeys.map(async function(privateKey) {
if (!privateKey.isPrivate()) {
throw new Error('Need private key for signing');
}
if (privateKey.hasSameFingerprintAs(primaryKey)) {
throw new Error("The user's own key can only be used for self-certifications");
}
const signingKey = await privateKey.getSigningKey(undefined, date, undefined, config);
return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, {
// Most OpenPGP implementations use generic certification (0x10)
signatureType: enums.signature.certGeneric,
keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData]
}, date, undefined, undefined, undefined, config);
}));
await user.update(this, date, config);
return user;
}
/**
* Checks if a given certificate of the user is revoked
* @param {SignaturePacket} certificate - The certificate to verify
* @param {PublicSubkeyPacket|
* SecretSubkeyPacket|
* PublicKeyPacket|
* SecretKeyPacket} [keyPacket] The key packet to verify the signature, instead of the primary key
* @param {Date} [date] - Use the given date for verification instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<Boolean>} True if the certificate is revoked.
* @async
*/
async isRevoked(certificate, keyPacket, date = new Date(), config = defaultConfig) {
const primaryKey = this.mainKey.keyPacket;
return isDataRevoked(primaryKey, enums.signature.certRevocation, {
key: primaryKey,
userID: this.userID,
userAttribute: this.userAttribute
}, this.revocationSignatures, certificate, keyPacket, date, config);
}
/**
* Verifies the user certificate.
* @param {SignaturePacket} certificate - A certificate of this user
* @param {Array<PublicKey>} verificationKeys - Array of keys to verify certificate signatures
* @param {Date} [date] - Use the given date instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<true|null>} true if the certificate could be verified, or null if the verification keys do not correspond to the certificate
* @throws if the user certificate is invalid.
* @async
*/
async verifyCertificate(certificate, verificationKeys, date = new Date(), config) {
const that = this;
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
const { issuerKeyID } = certificate;
const issuerKeys = verificationKeys.filter(key => key.getKeys(issuerKeyID).length > 0);
if (issuerKeys.length === 0) {
return null;
}
await Promise.all(issuerKeys.map(async key => {
const signingKey = await key.getSigningKey(issuerKeyID, certificate.created, undefined, config);
if (certificate.revoked || await that.isRevoked(certificate, signingKey.keyPacket, date, config)) {
throw new Error('User certificate is revoked');
}
try {
await certificate.verify(signingKey.keyPacket, enums.signature.certGeneric, dataToVerify, date, undefined, config);
} catch (e) {
throw util.wrapError('User certificate is invalid', e);
}
}));
return true;
}
/**
* Verifies all user certificates
* @param {Array<PublicKey>} verificationKeys - Array of keys to verify certificate signatures
* @param {Date} [date] - Use the given date instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<Array<{
* keyID: module:type/keyid~KeyID,
* valid: Boolean | null
* }>>} List of signer's keyID and validity of signature.
* Signature validity is null if the verification keys do not correspond to the certificate.
* @async
*/
async verifyAllCertifications(verificationKeys, date = new Date(), config) {
const that = this;
const certifications = this.selfCertifications.concat(this.otherCertifications);
return Promise.all(certifications.map(async certification => ({
keyID: certification.issuerKeyID,
valid: await that.verifyCertificate(certification, verificationKeys, date, config).catch(() => false)
})));
}
/**
* Verify User. Checks for existence of self signatures, revocation signatures
* and validity of self signature.
* @param {Date} date - Use the given date instead of the current time
* @param {Object} config - Full configuration
* @returns {Promise<true>} Status of user.
* @throws {Error} if there are no valid self signatures.
* @async
*/
async verify(date = new Date(), config) {
if (!this.selfCertifications.length) {
throw new Error('No self-certifications found');
}
const that = this;
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
// TODO replace when Promise.some or Promise.any are implemented
let exception;
for (let i = this.selfCertifications.length - 1; i >= 0; i--) {
try {
const selfCertification = this.selfCertifications[i];
if (selfCertification.revoked || await that.isRevoked(selfCertification, undefined, date, config)) {
throw new Error('Self-certification is revoked');
}
try {
await selfCertification.verify(primaryKey, enums.signature.certGeneric, dataToVerify, date, undefined, config);
} catch (e) {
throw util.wrapError('Self-certification is invalid', e);
}
return true;
} catch (e) {
exception = e;
}
}
throw exception;
}
/**
* Update user with new components from specified user
* @param {User} sourceUser - Source user to merge
* @param {Date} date - Date to verify the validity of signatures
* @param {Object} config - Full configuration
* @returns {Promise<undefined>}
* @async
*/
async update(sourceUser, date, config) {
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
// self signatures
await mergeSignatures(sourceUser, this, 'selfCertifications', date, async function(srcSelfSig) {
try {
await srcSelfSig.verify(primaryKey, enums.signature.certGeneric, dataToVerify, date, false, config);
return true;
} catch (e) {
return false;
}
});
// other signatures
await mergeSignatures(sourceUser, this, 'otherCertifications', date);
// revocation signatures
await mergeSignatures(sourceUser, this, 'revocationSignatures', date, function(srcRevSig) {
return isDataRevoked(primaryKey, enums.signature.certRevocation, dataToVerify, [srcRevSig], undefined, undefined, date, config);
});
}
/**
* Revokes the user
* @param {SecretKeyPacket} primaryKey - decrypted private primary key for revocation
* @param {Object} reasonForRevocation - optional, object indicating the reason for revocation
* @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation
* @param {String} reasonForRevocation.string optional, string explaining the reason for revocation
* @param {Date} date - optional, override the creationtime of the revocation signature
* @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<User>} New user with revocation signature.
* @async
*/
async revoke(
primaryKey,
{
flag: reasonForRevocationFlag = enums.reasonForRevocation.noReason,
string: reasonForRevocationString = ''
} = {},
date = new Date(),
config = defaultConfig
) {
const dataToSign = {
userID: this.userID,
userAttribute: this.userAttribute,
key: primaryKey
};
const user = new User(dataToSign.userID || dataToSign.userAttribute, this.mainKey);
user.revocationSignatures.push(await createSignaturePacket(dataToSign, null, primaryKey, {
signatureType: enums.signature.certRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString
}, date, undefined, undefined, false, config));
await user.update(this);
return user;
}
}
export default User;