Skip to content

Commit

Permalink
jwt: calculate kid from public key
Browse files Browse the repository at this point in the history
# Background

Recert resigns JWTs found in the cluster. The kid field in the
Kubernetes JWT header is a Base64URL encoded hash of the public key used
to sign the JWT. The kid used by verifiers (e.g. kube-apiserver) to
quickly identify the key used to sign the JWT, instead of brute-forcing.

# Issue

Likely to solve issues.redhat.com/browse/OCPBUGS-49972

The current implementation uses the SHA256 hash of the private key to
calculate the kid. This is inconsistent with the Kubernetes
implementation [1] which uses the public key to calculate the kid.

In recent versions of Kubernetes, a kid mismatch leads to "unauthorized"
errors, which eventually leads to a collapse of the cluster.

# Solution

This commit changes the kid calculation to use the SHA256 hash of the
public key instead of the private key, so that it becomes more
consistent with Kubernetes

# Backwards compatibility

Seeds processed with older versions of recert that don't contain this
fix should still work if "rejuvenated" by newer versions of recert which
do contain this fix. This is because the new version will simply
overwrite the `kid` field with the correct value.

# References

[1] https://github.com/openshift/kubernetes/blob/6fdacf04117cef54a0babd0945e8ef87d0f9461d/pkg/serviceaccount/jwt.go#L92-L112

Signed-off-by: Omer Tuchfeld <omer@tuchfeld.dev>
  • Loading branch information
omertuc committed Feb 20, 2025
1 parent 18d3284 commit 9bb96a2
Showing 1 changed file with 21 additions and 3 deletions.
24 changes: 21 additions & 3 deletions src/cluster_crypto/crypto_utils/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::cluster_crypto::{
};
use anyhow::{bail, Context, Result};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD as base64_url, Engine as _};
use bcder::{encode::Values, BitString};
use std::{io::Write, process::Command};
use x509_certificate::InMemorySigningKeyPair;
use x509_certificate::{rfc5280, InMemorySigningKeyPair, KeyAlgorithm, Sign};

pub(crate) fn verify(jwt: &str, public_key: &PublicKey) -> Result<bool> {
if let PublicKey::Ec(_) = public_key {
Expand Down Expand Up @@ -90,8 +91,25 @@ pub(crate) fn resign(jwt: &str, private_key: &SigningKey) -> Result<String> {
InMemorySigningKeyPair::Ed25519(_) => {
bail!("ed unsupported");
}
InMemorySigningKeyPair::Rsa(_rsa_key_pair, bytes) => (
base64_url.encode(crypto_utils::sha256(bytes).context("calculating kid")?),
InMemorySigningKeyPair::Rsa(_rsa_key_pair, _bytes) => (
// https://github.com/openshift/kubernetes/blob/6fdacf04117cef54a0babd0945e8ef87d0f9461d/pkg/serviceaccount/jwt.go#L92-L112
{
let keyinfo = rfc5280::SubjectPublicKeyInfo {
algorithm: KeyAlgorithm::Rsa.into(),
subject_public_key: BitString::new(0, private_key.in_memory_signing_key_pair.public_key_data()),
};

let pkix_der_bytes = {
let mut buffer: Vec<u8> = Vec::new();
keyinfo
.encode_ref()
.write_encoded(bcder::Mode::Der, &mut buffer)
.context("encode")?;
buffer
};

base64_url.encode(crypto_utils::sha256(pkix_der_bytes.as_slice()).context("hash")?)
},
private_key.pkcs8_pem.clone(),
),
};
Expand Down

0 comments on commit 9bb96a2

Please sign in to comment.