Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement WebRTC certificate storage in the keychain #3048

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Crosstons
Copy link

@Crosstons Crosstons commented Mar 16, 2025

Title

feat: Implement WebRTC certificate persistence

Description

This PR implements the WebRTC certificate persistence functionality. The following changes have been made:

  • Added certificate-store.ts to handle storing and retrieving certificates from the keychain.
  • Created tests for certificate persistence in certificate-persistence.spec.ts.

This addresses the issue of ensuring certificates can be stored and reused across node restarts, improving the reliability and efficiency of WebRTC connections.

Related #2988.

Notes & open questions

  • The test file certificate-persistence.spec.ts contains two tests. One test passes successfully, while the second test related to persisting certificates between restarts still needs changes. Further debugging and adjustments are required to ensure all tests pass.

Change checklist

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation if necessary (this includes comments as well)
  • I have added tests that prove my fix is effective or that my feature works

@Crosstons Crosstons requested a review from a team as a code owner March 16, 2025 11:29
@achingbrain achingbrain changed the title feat: implement WebRTC certificate sotorage in the keychain feat: implement WebRTC certificate storage in the keychain Mar 24, 2025
Copy link
Member

@achingbrain achingbrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening this. As implemented it won't actually persist the certificate as it sets it as a _certificate field on a private key.

As of #3059 we can now generate and store ECDSA keypairs as PrivateKeys and as of #3062 we can store PEM-encoded x509 certificates in the keychain which you may have been missing.

Please can you update the implementation to use these new methods.

try {
// Check if the private key has our expected certificate metadata
// @ts-expect-error - We're checking for custom properties added during storage
if (privateKey._certificate == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field will not be present when the key is deserialized by the keychain.

// Create a node with certificate persistence enabled
const node = await createNode(null, {
certificates: [], // Empty array to not use any pre-defined certificates
useLibjuice: false, // Use WebRTC's built-in STUN/TURN
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option is ignored, it will only use libjuice now.

Suggested change
useLibjuice: false, // Use WebRTC's built-in STUN/TURN

Comment on lines +84 to +86
rtcConfiguration: {
iceTransportPolicy: 'all',
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this config is relevant to the test.

Suggested change
rtcConfiguration: {
iceTransportPolicy: 'all',
}

],
services: {
keychain: async (components) => {
const keychainModule = await import('@libp2p/keychain')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the dynamic import necessary?

@@ -0,0 +1,176 @@
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this test imports libp2p and other modules, it should be moved to /packages/integration-tests in this monorepo.

Only unit tests should be added here. Obviously the definition is hazy but if you're starting a libp2p node or having to jump through hoops to import other modules from the monorepo, it's probably an integration test.

const validityDays = options.validityDays ?? DEFAULT_CERTIFICATE_VALIDITY_DAYS

log?.trace('generating new ECDSA P-256 key pair')
const keyPair = await crypto.subtle.generateKey({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can now import generateKeyPair from @libp2p/crypto/keys and do await generateKeyPair('ECDSA').

Then the keypair can be stored in the keychain and you can use privateKeyToCryptoKeyPair (also imported from @libp2p/crypto/keys) to get a WebCrypto-friendly keypair to generate the cert.

Comment on lines +49 to +53
connectionGater: {
denyDialMultiaddr: async () => false
},
streamMuxers: [mplex()],
connectionEncrypters: [noise()],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to supply a muxer/encrypter?

Comment on lines +55 to +60
webRTCDirect({
...options,
dataChannel: {
maxMessageSize: 1 << 16
}
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to set the message size?

await node.start()

// Wait for addresses to be available
await new Promise(resolve => setTimeout(resolve, 1000))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't add arbitrary delays, they make the tests take a long time to run and can be flakey.

Instead, use something like p-wait-for and test for the thing you are waiting for.

* This test checks that the WebRTC transport is at least attempting to persist and retrieve certificates.
* After fixing the createPrivateKeyFromCertificate implementation, it should now work properly.
*/
it('should attempt certificate persistence between restarts', async function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should just start a node, record any WebRTC-Direct multiaddrs it's listening on, then stop the node and start it again and assert that it's re-used the same cert. No need for deep stubbing of module internals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants