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

Add certificatesRequests store validation #1893

Closed
Tracked by #1902
leblowl opened this issue Oct 3, 2023 · 6 comments
Closed
Tracked by #1902

Add certificatesRequests store validation #1893

leblowl opened this issue Oct 3, 2023 · 6 comments
Assignees
Labels
bug Something isn't working needs migration Needs data migration code for backward compatibility security

Comments

@leblowl
Copy link
Collaborator

leblowl commented Oct 3, 2023

We should verify that each CSR is signed by pubKey (see: #1892) and validate usernames, etc.

This prevents users from forging other users' CSRs, overwriting other users' username requests and from adding invalid usernames.

Currently, with the additions of #1843, CSRs are replicated:

this.emit(StorageEvents.REPLICATED_CSR, { csrs: allCsrs, certificates: allCertificates })

And via RESPONSE_GET_CSRS, these CSRs are sent to the frontend:

this.storageService.on(
StorageEvents.REPLICATED_CSR,
async (payload: { csrs: string[]; certificates: string[] }) => {
console.log(`On ${StorageEvents.REPLICATED_CSR}`)
this.serverIoProvider.io.emit(SocketActionTypes.RESPONSE_GET_CSRS, { csrs: payload.csrs })
this.registrationService.emit(RegistrationEvents.REGISTER_USER_CERTIFICATE, payload)
}
)

CSRs are indexed by public key:

export const csrsMapping = createSelector(csrs, csrs => {

Where the public key is extracted from the decoded cert:

export const parseCertificationRequest = (pem: string): CertificationRequest => {

return Buffer.from(certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex).toString('base64')

These CSRs are then used for associating username with users:

export const allUsers = createSelector(csrsMapping, certificatesMapping, (csrs, certs) => {

It appears that the CSR isn't validated in this example, so you could BER encode a CSR with someone else's public key and since we don't check the signature, we wouldn't notice.

Following the other path that CSRs take, they get signed by the owner via REGISTER_USER_CERTIFICATE. In this path, first they are decoded:

export const loadCSR = async (csr: string): Promise<CertificationRequest> => {
const certBuffer = stringToArrayBuffer(fromBase64(csr))
const asn1 = fromBER(certBuffer)
return new CertificationRequest({ schema: asn1.result })

They are then filtered so only CSRs with unregistered names are prepared for signing:

export const extractPendingCsrs = async (payload: { csrs: string[]; certificates: string[] }) => {

Then each filtered CSR is signed:

export const issueCertificate = async (userCsr: string, permsData: PermsData): Promise<RegistrarResponse> => {

Inside issueCertificate, there is a validateCsr function, but that only appears to validate the BER encoding and basic structural properties:

export const validateCsr = async (csr: string) => {
const userData = new UserCsrData()
userData.csr = csr
const validationErrors = await validate(userData)
return validationErrors
}

class UserCsrData {
@IsNotEmpty()
@IsBase64()
@IsCsr()
@CsrContainsFields()
csr: string

generateUserCert also doesn't appear to validate the CSR signature before signing:

export const createUserCert = async (

So it looks like anyone could send a CSR with another user's public key in the CSR and an invalid signature (because they don't control that public key) and thus change another user's username.

@holmesworcester
Copy link
Contributor

How would overwriting others' usernames work? Do you mean submitting a request for the name alice right after someone else submits a request for the name alice? I think we can't stop that with an access controller. Or do you mean something else?

@leblowl leblowl added the needs migration Needs data migration code for backward compatibility label Oct 3, 2023
@leblowl
Copy link
Collaborator Author

leblowl commented Oct 3, 2023

@holmesworcester I added additional details to the issue. Basically, if we don't check CSR signatures then I think anyone can put someone else's public key and a random username in a CSR (the CSR signature won't be valid, but we don't check that) then get a certificate issued for it, thus changing another user's username. The signature of the CSR needs to match the CSR data and be signed by the public key included in the CSR data, otherwise you can just include any public key in the CSR.

@holmesworcester
Copy link
Contributor

Note: we would do this together with #1892

@leblowl
Copy link
Collaborator Author

leblowl commented Oct 5, 2023

In order to validate a CSR, we need to check:

  • that the CSR is valid (parses correctly, formatted correctly, contains required fields, etc.)
  • that the CSR is signed by the public key included in the CSR
  • that the username in the CSR is valid (ASCII a-z, dashes and numbers)
  • TODO: validate other fields in CSR?

To implement this validation, we could use an access controller, but it looks like access controllers only check heads and we need to validate each entry. As an alternative, we can create a new validation function validateCsrEntry that, given an entry, validates the entry according the specs above (and calls Entry.verify) and returns true/false. Then we can create a new function, getCsrs that iterates over the certificatesRequests log entries, and filters the logs with validateCsrEntry such that it only returns valid entries. We can then use getCsrs to send valid CSRs to other parts of the application for processing. Also logic from the state-manager that retrieves the latest CSR for each public key should be moved into getCsrs on the backend.

With this approach we will still be caching and replicating invalid entries in IPFS, however when we implement permanent deletion, we can revisit that.

This approach also involves re-validating invalid entries when iterating over the log successively. That's not ideal, but I think we can optimize it in the future.

It might be nice to encapsulate everything in a CertificateRequestStore class. See also: #1923

@holmesworcester
Copy link
Contributor

I think the validation should lowercase letters only, hyphens allowed, and numbers

@leblowl leblowl changed the title Add access controller to certificatesRequests Add certificatesRequests validation Oct 6, 2023
@leblowl leblowl changed the title Add certificatesRequests validation Add certificatesRequests store validation Oct 7, 2023
@vinkabuki vinkabuki moved this from Sprint to In progress in Quiet Oct 20, 2023
@vinkabuki vinkabuki self-assigned this Oct 20, 2023
@vinkabuki vinkabuki moved this from In progress to Waiting for review in Quiet Nov 28, 2023
@siepra siepra moved this from Waiting for review to Merged (develop) in Quiet Nov 29, 2023
@Kacper-RF Kacper-RF moved this from Merged (develop) to Ready for QA in Quiet Dec 1, 2023
@kingalg
Copy link
Collaborator

kingalg commented Jan 11, 2024

Desktop: [email protected]
Mobile: [email protected], ios 344

@kingalg kingalg closed this as completed Jan 11, 2024
@kingalg kingalg moved this from Ready for QA to Done in Quiet Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs migration Needs data migration code for backward compatibility security
Projects
Archived in project
Development

No branches or pull requests

4 participants