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

Ambiguous CSR behavior: Fulcio accepts CSRs with email subjects that aren't emails #863

Closed
woodruffw opened this issue Oct 27, 2022 · 12 comments · Fixed by #926
Closed

Ambiguous CSR behavior: Fulcio accepts CSRs with email subjects that aren't emails #863

woodruffw opened this issue Oct 27, 2022 · 12 comments · Fixed by #926
Labels
bug Something isn't working

Comments

@woodruffw
Copy link
Member

I noticed this while doing a read-through of sigstore-python's signing code: we currently construct a CSR like so:

        oidc_identity = Identity(identity_token)

        # Build an X.509 Certificiate Signing Request
        builder = (
            x509.CertificateSigningRequestBuilder()
            .subject_name(
                x509.Name(
                    [
                        x509.NameAttribute(NameOID.EMAIL_ADDRESS, oidc_identity.proof),
                    ]
                )
            )
            .add_extension(
                x509.BasicConstraints(ca=False, path_length=None),
                critical=True,
            )
        )
        certificate_request = builder.sign(private_key, hashes.SHA256())

...where oidc_identity.proof is the value of either the email or sub claim, depending on the kind of OIDC JWT being used.

For OIDC JWTs that attest to email identities, this makes sense: we're sending a CSR with a SubjectName that's an email address. It doesn't make sense for OIDC JWTs that attest to URLs, since those aren't email addresses.

However, it currently works for both: sigstore-python currently uses NameOID.EMAIL_ADDRESS unconditionally, and Fulcio has been happily minting certs even when the SubjectName is really a URL (like a GitHub Actions workflow reference).

This probably isn't exploitable or dangerous in any way, since the CSR is otherwise well-formed and accompanied by a valid OIDC JWT and proof of possession for the signing key. But it's a little bit surprising, and suggests that Fulcio's CSR validation is a little looser than it should be.

IMO, there are (at least) two acceptable alternative behaviors here:

  • Ignore the SubjectName in the CSR entirely, and (after a deprecation period) have users set it to something well-known, like sigstore-csr instead of an email address with an email OID. Then, explicitly document the behavior of re-deriving the subject from the adjacent JWT.
  • Continue to handle SubjectName, but enforce the correct OID type for the content of the subject. In other words: SubjectNames that are tagged as emails should look like emails, URLs should be UNSTRUCTURED_NAME or something similar, or maybe something custom.
@woodruffw woodruffw added the bug Something isn't working label Oct 27, 2022
@woodruffw
Copy link
Member Author

cc @di for visibility.

@haydentherapper
Copy link
Contributor

haydentherapper commented Oct 28, 2022

I'd actually say this is working as intended.

https://github.com/sigstore/fulcio/blob/main/fulcio.proto#L102 notes that all values, besides the public key, are ignored in the CSR. As for the proof of possession challenge, we don't care what you are signing over, just that you can generate a valid signature using a private key that can be verified with the CSRs public key. You could put total gibberish in the CSR if you wanted, as long as a) the public key is valid, b) the signature over the contents of the CSR is valid.

FWIW, maybe there's an attack vector I'm missing, but in the other case of providing the public key, we probably could have designed this to just have the client sign a static string like the URL - What's being signed over doesn't matter, just that we can validate the signature.

I considered having stricter validation that the CSR subject matches the subject in the OIDC token, but I decided it was an unnecessary error case. The only purpose for the CSR is to simplify key delivery for clients that can generate CSRs and don't want to deal with key encodings.

@haydentherapper
Copy link
Contributor

Addendum: Some code snippets:

@woodruffw
Copy link
Member Author

Thanks for explaining, and for the code snippets! It's good to know all of those fields are explicitly ignored.

Yeah, I see no actual attack vector here -- it's just something that stood out to me during a review of our own code on sigstore-python. I'm always in favor of stricter validation where possible, but I'm not too familiar with the CSR ecosystem and I can understand that not being worth the pain of dealing with clients who generate less precise CSRs.

Closing!

@znewman01
Copy link
Contributor

From RFC 2986 (section 3) which describes PKCS#10:

Note 2 - The signature on the certification request prevents an
entity from requesting a certificate with another party's public key.
Such an attack would give the entity the minor ability to pretend to
be the originator of any message signed by the other party. This
attack is significant only if the entity does not know the message
being signed and the signed part of the message does not identify the
signer. The entity would still not be able to decrypt messages
intended for the other party, of course.

I agree with the description of this as a "minor ability," but it does feel odd
to deviate from the standard, especially because for the other
proof-of-posession we do require that the subject winds up inside the signed
data.

Reopening because I think we should reconsider and try to be consistent here, though I think we could get away with doing nothing.

@znewman01 znewman01 reopened this Dec 8, 2022
@haydentherapper
Copy link
Contributor

Agreed that it seems very minor. Also, since an attacker would already need to possess the OIDC token, the subject that's being signed over is already known. I don't see the threat vector with signing over a CSR without a subject.

The other issue is this would be a breaking change to start enforcing the subject in the CSR.

@znewman01
Copy link
Contributor

Also, since an attacker would already need to possess the OIDC token, the subject that's being signed over is already known. I don't see the threat vector with signing over a CSR without a subject.

IIUC the risk is that I take your CSR without a subject and stick it next to my OIDC token. So I can get a cert when I don't control the corresponding private key. I don't know why this is useful to an attacker though.

(Maybe that's exactly what you were saying.)

The other issue is this would be a breaking change to start enforcing the subject in the CSR.

Do we have any information on usage here? I'd be curious to see whether anybody is actually sending CSRs.


But I think I'd be okay with just documenting this better (API docs, inline comment, spec).

@woodruffw
Copy link
Member Author

Do we have any information on usage here? I'd be curious to see whether anybody is actually sending CSRs.

CSRs are how sigstore-python currently requests certs from Fulcio, but I have no idea if we're unique in that regard 🙂

@haydentherapper
Copy link
Contributor

Yea, I think for now the best we can do is better documentation.

@woodruffw
Copy link
Member Author

SGTM! I agree that the behavior here is not exploitable in its current form, so documentation seems appropriate.

@haydentherapper if you can point me at the right doc, I'm happy to make those changes 🙂

@haydentherapper
Copy link
Contributor

Thanks! https://github.com/sigstore/fulcio/blob/main/fulcio_legacy.proto#L105 and https://github.com/sigstore/fulcio/blob/main/fulcio.proto#L105, and then regenerate the swagger docs with make all.

@woodruffw
Copy link
Member Author

Thanks as well, will do momentarily.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants