Skip to content

Commit da4e9da

Browse files
authored
fix: do not store x509 certs in the keychain (#3069)
x509 certs are only public certs so don't need to be stored in an encrypted format. Reverts #3062 (unreleased so not a breaking change)
1 parent bec05ed commit da4e9da

File tree

6 files changed

+22
-295
lines changed

6 files changed

+22
-295
lines changed

packages/keychain/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
},
7272
"devDependencies": {
7373
"@libp2p/logger": "^5.1.13",
74-
"@peculiar/x509": "^1.12.3",
7574
"aegir": "^45.1.1",
7675
"datastore-core": "^10.0.2"
7776
},

packages/keychain/src/index.ts

-33
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,6 @@ export interface KeyInfo {
105105
name: string
106106
}
107107

108-
export interface X509Info {
109-
/**
110-
* The local key name
111-
*/
112-
name: string
113-
}
114-
115108
export interface Keychain {
116109
/**
117110
* Find a key by name
@@ -208,32 +201,6 @@ export interface Keychain {
208201
*/
209202
listKeys(): Promise<KeyInfo[]>
210203

211-
/**
212-
* Import an X509 certificate in PEM format
213-
*/
214-
importX509 (name: string, pem: string): Promise<void>
215-
216-
/**
217-
* Export an X509 certificate in PEM format
218-
*/
219-
exportX509 (name: string): Promise<string>
220-
221-
/**
222-
* Removes an X509 certificate from the keychain
223-
*/
224-
removeX509 (name: string): Promise<void>
225-
226-
/**
227-
* List all certificates.
228-
*
229-
* @example
230-
*
231-
* ```TypeScript
232-
* const certs = await libp2p.keychain.listX509()
233-
* ```
234-
*/
235-
listX509(): Promise<X509Info[]>
236-
237204
/**
238205
* Rotate keychain password and re-encrypt all associated keys
239206
*

packages/keychain/src/keychain.ts

+22-125
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ import { sha256 } from 'multiformats/hashes/sha2'
1010
import sanitize from 'sanitize-filename'
1111
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
1212
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
13-
import { exportPrivateKey, exporter } from './utils/export.js'
14-
import { importPrivateKey, importer } from './utils/import.js'
15-
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo, X509Info } from './index.js'
13+
import { exportPrivateKey } from './utils/export.js'
14+
import { importPrivateKey } from './utils/import.js'
15+
import type { KeychainComponents, KeychainInit, Keychain as KeychainInterface, KeyInfo } from './index.js'
1616
import type { Logger, PrivateKey } from '@libp2p/interface'
1717

1818
const keyPrefix = '/pkcs8/'
19-
const certPrefix = '/x509/'
2019
const infoPrefix = '/info/'
2120
const privates = new WeakMap<object, { dek: string }>()
2221

@@ -64,8 +63,8 @@ async function randomDelay (): Promise<void> {
6463
/**
6564
* Converts a key name into a datastore name
6665
*/
67-
function DsName (prefix: string, name: string): Key {
68-
return new Key(prefix + name)
66+
function DsName (name: string): Key {
67+
return new Key(keyPrefix + name)
6968
}
7069

7170
/**
@@ -207,7 +206,7 @@ export class Keychain implements KeychainInterface {
207206
await randomDelay()
208207
throw new InvalidParametersError('Key is required')
209208
}
210-
const datastoreName = DsName(keyPrefix, name)
209+
const datastoreName = DsName(name)
211210
const exists = await this.components.datastore.has(datastoreName)
212211
if (exists) {
213212
await randomDelay()
@@ -249,7 +248,7 @@ export class Keychain implements KeychainInterface {
249248
throw new InvalidParametersError(`Invalid key name '${name}'`)
250249
}
251250

252-
const datastoreName = DsName(keyPrefix, name)
251+
const datastoreName = DsName(name)
253252
try {
254253
const res = await this.components.datastore.get(datastoreName)
255254
const pem = uint8ArrayToString(res)
@@ -274,7 +273,7 @@ export class Keychain implements KeychainInterface {
274273
throw new InvalidParametersError(`Invalid key name '${name}'`)
275274
}
276275

277-
const datastoreName = DsName(keyPrefix, name)
276+
const datastoreName = DsName(name)
278277
const keyInfo = await this.findKeyByName(name)
279278
const batch = this.components.datastore.batch()
280279
batch.delete(datastoreName)
@@ -318,8 +317,8 @@ export class Keychain implements KeychainInterface {
318317
await randomDelay()
319318
throw new InvalidParametersError(`Invalid new key name '${newName}'`)
320319
}
321-
const oldDatastoreName = DsName(keyPrefix, oldName)
322-
const newDatastoreName = DsName(keyPrefix, newName)
320+
const oldDatastoreName = DsName(oldName)
321+
const newDatastoreName = DsName(newName)
323322
const oldInfoName = DsInfoName(oldName)
324323
const newInfoName = DsInfoName(newName)
325324

@@ -348,99 +347,6 @@ export class Keychain implements KeychainInterface {
348347
}
349348
}
350349

351-
/**
352-
* List all the certificates
353-
*/
354-
async listX509 (): Promise<X509Info[]> {
355-
const query = {
356-
prefix: certPrefix
357-
}
358-
359-
const info = []
360-
for await (const value of this.components.datastore.query(query)) {
361-
info.push({
362-
name: value.key.toString().replace(certPrefix, '')
363-
})
364-
}
365-
366-
return info
367-
}
368-
369-
async importX509 (name: string, pem: string): Promise<void> {
370-
try {
371-
if (!validateKeyName(name)) {
372-
throw new InvalidParametersError(`Invalid certificate name '${name}'`)
373-
}
374-
375-
if (pem == null) {
376-
throw new InvalidParametersError('PEM is required')
377-
}
378-
379-
if (!pem.includes('-----BEGIN CERTIFICATE-----') && !pem.includes('-----END CERTIFICATE-----')) {
380-
throw new InvalidParametersError('PEM was invalid')
381-
}
382-
383-
const datastoreName = DsName(certPrefix, name)
384-
385-
const exists = await this.components.datastore.has(datastoreName)
386-
if (exists) {
387-
throw new InvalidParametersError(`Certificate '${name}' already exists`)
388-
}
389-
390-
const cached = privates.get(this)
391-
392-
if (cached == null) {
393-
throw new InvalidParametersError('dek missing')
394-
}
395-
396-
const dek = cached.dek
397-
const dsPem = await exporter(uint8ArrayFromString(pem), dek)
398-
await this.components.datastore.put(datastoreName, uint8ArrayFromString(dsPem))
399-
} catch (err) {
400-
await randomDelay()
401-
throw err
402-
}
403-
}
404-
405-
async exportX509 (name: string): Promise<string> {
406-
try {
407-
if (!validateKeyName(name)) {
408-
throw new InvalidParametersError(`Invalid key name '${name}'`)
409-
}
410-
411-
const datastoreName = DsName(certPrefix, name)
412-
const res = await this.components.datastore.get(datastoreName)
413-
const encryptedPem = uint8ArrayToString(res)
414-
const cached = privates.get(this)
415-
416-
if (cached == null) {
417-
throw new InvalidParametersError('dek missing')
418-
}
419-
420-
const dek = cached.dek
421-
const buf = await importer(encryptedPem, dek)
422-
423-
return uint8ArrayToString(buf)
424-
} catch (err: any) {
425-
await randomDelay()
426-
throw err
427-
}
428-
}
429-
430-
async removeX509 (name: string): Promise<void> {
431-
try {
432-
if (!validateKeyName(name) || name === this.self) {
433-
throw new InvalidParametersError(`Invalid key name '${name}'`)
434-
}
435-
436-
const datastoreName = DsName(certPrefix, name)
437-
await this.components.datastore.delete(datastoreName)
438-
} catch (err) {
439-
await randomDelay()
440-
throw err
441-
}
442-
}
443-
444350
/**
445351
* Rotate keychain password and re-encrypt all associated keys
446352
*/
@@ -475,33 +381,24 @@ export class Keychain implements KeychainInterface {
475381
this.init.dek?.hash)
476382
: ''
477383
privates.set(this, { dek: newDek })
478-
479-
const batch = this.components.datastore.batch()
480-
481-
for (const key of await this.listKeys()) {
482-
const res = await this.components.datastore.get(DsName(keyPrefix, key.name))
384+
const keys = await this.listKeys()
385+
for (const key of keys) {
386+
const res = await this.components.datastore.get(DsName(key.name))
483387
const pem = uint8ArrayToString(res)
484388
const privateKey = await importPrivateKey(pem, oldDek)
485389
const password = newDek.toString()
486390
const keyAsPEM = await exportPrivateKey(privateKey, password, privateKey.type === 'RSA' ? 'pkcs-8' : 'libp2p-key')
487391

488-
// add to batch
489-
batch.put(DsName(keyPrefix, key.name), uint8ArrayFromString(keyAsPEM))
490-
}
491-
492-
for (const key of await this.listX509()) {
493-
// decrypt using old password and encrypt using new
494-
const res = await this.components.datastore.get(DsName(certPrefix, key.name))
495-
const pem = uint8ArrayToString(res)
496-
const decrypted = await importer(pem, oldDek)
497-
const encrypted = await exporter(decrypted, newDek)
498-
499-
// add to batch
500-
batch.put(DsName(certPrefix, key.name), uint8ArrayFromString(encrypted))
392+
// Update stored key
393+
const batch = this.components.datastore.batch()
394+
const keyInfo = {
395+
name: key.name,
396+
id: key.id
397+
}
398+
batch.put(DsName(key.name), uint8ArrayFromString(keyAsPEM))
399+
batch.put(DsInfoName(key.name), uint8ArrayFromString(JSON.stringify(keyInfo)))
400+
await batch.commit()
501401
}
502-
503-
await batch.commit()
504-
505402
this.log('keychain reconstructed')
506403
}
507404
}

packages/keychain/test/certificates.spec.ts

-102
This file was deleted.

packages/keychain/test/fixtures/create-certificate.ts

-25
This file was deleted.

0 commit comments

Comments
 (0)