Skip to content

Commit

Permalink
Add additional support for x5c and validate x5c against the public ke…
Browse files Browse the repository at this point in the history
…y in an JSON Web Key when parsing.
  • Loading branch information
robotdan committed Oct 14, 2020
1 parent f6557d2 commit fd761c6
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 4 deletions.
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
FusionAuth JWT Changes

Changes in 3.6.0

* Add the x5c to the JSON Web Key Builder
* When provided, use the x5c JSON Web Key property to verify the public key modulus and exponent.

Changes in 3.5.3

* Add JWK Thumbprint
Expand Down
2 changes: 1 addition & 1 deletion build.savant
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
savantVersion = "1.0.0"
jacksonVersion = "2.10.3"

project(group: "io.fusionauth", name: "fusionauth-jwt", version: "3.5.3", licenses: ["ApacheV2_0"]) {
project(group: "io.fusionauth", name: "fusionauth-jwt", version: "3.6.0", licenses: ["ApacheV2_0"]) {

workflow {
standard()
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/io/fusionauth/jwks/JSONWebKeyBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2019, FusionAuth, All Rights Reserved
* Copyright (c) 2018-2020, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -40,6 +40,7 @@
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECPoint;
import java.util.Base64;
import java.util.Collections;
import java.util.Objects;

import static io.fusionauth.der.ObjectIdentifier.ECDSA_P256;
Expand Down Expand Up @@ -185,6 +186,7 @@ public JSONWebKey build(Certificate certificate) {
key.alg = Algorithm.fromName(((X509Certificate) certificate).getSigAlgName());
try {
String encodedCertificate = new String(Base64.getEncoder().encode(certificate.getEncoded()));
key.x5c = Collections.singletonList(encodedCertificate);
key.x5t = JWTUtils.generateJWS_x5t(encodedCertificate);
key.x5t_256 = JWTUtils.generateJWS_x5t("SHA-256", encodedCertificate);
} catch (CertificateEncodingException e) {
Expand Down
32 changes: 30 additions & 2 deletions src/main/java/io/fusionauth/jwks/JSONWebKeyParser.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2019, FusionAuth, All Rights Reserved
* Copyright (c) 2018-2020, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,11 +18,14 @@

import io.fusionauth.jwks.domain.JSONWebKey;
import io.fusionauth.jwt.domain.KeyType;
import io.fusionauth.pem.PEMEncoder;
import io.fusionauth.pem.domain.PEM;

import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
Expand Down Expand Up @@ -51,7 +54,30 @@ public PublicKey parse(JSONWebKey key) {
if (key.kty == KeyType.RSA) {
BigInteger modulus = base64DecodeUint(key.n);
BigInteger publicExponent = base64DecodeUint(key.e);
return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));

// If an x5c is found in the key, verify the public key
if (key.x5c != null && key.x5c.size() > 0) {
// The first key in this array MUST contain the public key.
// > https://tools.ietf.org/html/rfc7517#section-4.7
String encodedCertificate = key.x5c.get(0);
String pem = new PEMEncoder().parseEncodedCertificate(encodedCertificate);
PublicKey actual = PEM.decode(pem).publicKey;
if (!(actual instanceof RSAPublicKey)) {
throw new JSONWebKeyParserException("The public key found in the [x5c] property does not match the expected key type specified by the [kty] property.");
}

RSAPublicKey rsaPublicKey = (RSAPublicKey) actual;
if (!rsaPublicKey.getModulus().equals(modulus)) {
throw new JSONWebKeyParserException("Expected a modulus value of [" + modulus + "] but found [" + rsaPublicKey.getModulus() + "]. The certificate found in [x5c] does not match the [n] property.");
}

if (!rsaPublicKey.getPublicExponent().equals(publicExponent)) {
throw new JSONWebKeyParserException("Expected a public exponent value of [" + publicExponent + "] but found [" + rsaPublicKey.getPublicExponent() + "]. The certificate found in [x5c] does not match the [e] property.");
}
}

return publicKey;
} else if (key.kty == KeyType.EC) {
// EC Public key
AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
Expand All @@ -75,6 +101,8 @@ public PublicKey parse(JSONWebKey key) {
ECPoint ecPoint = new ECPoint(x, y);
return KeyFactory.getInstance("EC").generatePublic(new ECPublicKeySpec(ecPoint, ecParameterSpec));
}
} catch (JSONWebKeyParserException e) {
throw e;
} catch (Exception e) {
throw new JSONWebKeyParserException("Failed to parse the provided JSON Web Key", e);
}
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/io/fusionauth/pem/PEMEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.interfaces.ECPrivateKey;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

import static io.fusionauth.pem.domain.PEM.X509_CERTIFICATE_PREFIX;
import static io.fusionauth.pem.domain.PEM.X509_CERTIFICATE_SUFFIX;
Expand All @@ -45,6 +47,38 @@
public class PEMEncoder {
private static final Base64.Encoder Base64_MIME_Encoder = Base64.getMimeEncoder(64, new byte[]{'\n'});

private String removeLineReturns(String str) {
if (str == null) {
return null;
}

return str.replaceAll("\\r\\n|\\r|\\n", "");
}

/**
* Attempt to covert a ASN.1 DER encoded X.509 certificate into a PEM encoded string.
*
* @param derEncoded base64 ASN.1 DER encoded bytes of an X.509 certificate
* @return a PEM encoded certificate
*/
public String parseEncodedCertificate(String derEncoded) {
return PEM.X509_CERTIFICATE_PREFIX + "\n" + chopIt(derEncoded) + "\n" + PEM.X509_CERTIFICATE_SUFFIX;
}

private String chopIt(String s) {
List<String> lines = new ArrayList<>();

// The incoming string may or may not contain line returns, normalize first and then re-encode to 64 characters wide
String normalized = removeLineReturns(s);

for (int i = 0; i < normalized.length(); ) {
lines.add(normalized.substring(i, Math.min(i + 64, normalized.length())));
i = i + 64;
}

return String.join("\n", lines);
}

/**
* Encode the provided keys in a PEM format and return a string. If both private and public keys are provided a private
* key PEM will be returned with the public key embedded.
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/jwk/rsa_certificate_gd_bundle_g2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"kty": "RSA",
"n": "ueDLENSvdr3Uk2LrMGS4gQhswwTZYheOL_8-Zc-PzmLmPFIc2hZFS1WreGtjg2KQzg9pbJnIGhSLTMxFM-qI3J6jryv-gGGdeVfEzy70PzA8XUf8mha8wzeWQVGOEUtU-Ci-0Iy-8DA4HvOwJvhmR2Nt3nEmR484R1PRRh2049wA6kWsvbxx2apvANvbzTA6eU9fTEf4He9bwsSdYDuxskOR2KQzTuqz1idPrSWKpcb01dCmrnQFZFeItURV1C0qOj74uL3pMgoClGTEFjpQ8Uqu53kzrwwgB3_o3wQ5wmkCbGNS-nfBG8h0h8i5kxhQVDVLaU68O9NJLh_cwdJS-w",
"use": "sig",
"x5c" : [ "MIIE0DCCA7igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAwMFoXDTMxMDUwMzA3MDAwMFowgbQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjEtMCsGA1UECxMkaHR0cDovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMTMwMQYDVQQDEypHbyBEYWRkeSBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC54MsQ1K92vdSTYuswZLiBCGzDBNliF44v/z5lz4/OYuY8UhzaFkVLVat4a2ODYpDOD2lsmcgaFItMzEUz6ojcnqOvK/6AYZ15V8TPLvQ/MDxdR/yaFrzDN5ZBUY4RS1T4KL7QjL7wMDge87Am+GZHY23ecSZHjzhHU9FGHbTj3ADqRay9vHHZqm8A29vNMDp5T19MR/gd71vCxJ1gO7GyQ5HYpDNO6rPWJ0+tJYqlxvTV0KaudAVkV4i1RFXULSo6Pvi4vekyCgKUZMQWOlDxSq7neTOvDCAHf+jfBDnCaQJsY1L6d8EbyHSHyLmTGFBUNUtpTrw700kuH9zB0lL7AgMBAAGjggEaMIIBFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUQMK9J47MNIMwojPX+2yz8LQsgM4wHwYDVR0jBBgwFoAUOpqFBxBnKLbv9r0FQW4gwZTaD94wNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5nb2RhZGR5LmNvbS9nZHJvb3QtZzIuY3JsMEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEGCCsGAQUFBwIBFiVodHRwczovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAIfmyTEMg4uJapkEv/oV9PBO9sPpyIBslQj6Zz91cxG7685C/b+LrTW+C05+Z5Yg4MotdqY3MxtfWoSKQ7CC2iXZDXtHwlTxFWMMS2RJ17LJ3lXubvDGGqv+QqG+6EnriDfcFDzkSnE3ANkR/0yBOtg2DZ2HKocyQetawiDsoXiWJYRBuriSUBAA/NxBti21G00w9RKpv0vHP8ds42pM3Z2Czqrpv1KrKQ0U11GIo/ikGQI31bS/6kA1ibRrLDYGCD+H1QQc7CoZDDu+8CL9IVVO5EFdkKrqeKM+2xLXY2JtwE65/3YR8V3Idv7kaWKK2hJn0KCacuBKONvPi8BDAB" ],
"x5t": "J6yTafryUge7JifO-sy-TvnDGbg",
"x5t#S256": "lzpBJ2_9AeAnoqrUnjTDeEbT6Xb_amILZxLjODIEGqY"
}
1 change: 1 addition & 0 deletions src/test/resources/jwk/rsa_public_certificate_2048.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"kty": "RSA",
"n": "vIqz-4-ER_vNWLON9yv8hIYV737JQ6rCl6XfzOC628seYUPf0TaGk91CFxefhzh23V9Tkq-RtwN1Vs_z57hO82kkzL-cQHZX3bMJD-GEGOKXCEXURN7VMyZWMAuzQoW9vFb1k3cR1RW_EW_P-C8bb2dCGXhBYqPfHyimvz2WarXhntPSbM5XyS5v5yCw5T_Vuwqqsio3V8wooWGMpp61y12NhN8bNVDQAkDPNu2DT9DXB1g0CeFINp_KAS_qQ2Kq6TSvRHJqxRR68RezYtje9KAqwqx4jxlmVAQy0T3-T-IAbsk1wRtWDndhO6s1Os-dck5TzyZ_dNOhfXgelixLUQ",
"use": "sig",
"x5c" : [ "MIIC4jCCAcqgAwIBAgIQQNXrmzhLN4VGlUXDYCRT3zANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE0MTAyODAwMDAwMFoXDTE2MTAyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyKs/uPhEf7zVizjfcr/ISGFe9+yUOqwpel38zgutvLHmFD39E2hpPdQhcXn4c4dt1fU5KvkbcDdVbP8+e4TvNpJMy/nEB2V92zCQ/hhBjilwhF1ETe1TMmVjALs0KFvbxW9ZN3EdUVvxFvz/gvG29nQhl4QWKj3x8opr89lmq14Z7T0mzOV8kub+cgsOU/1bsKqrIqN1fMKKFhjKaetctdjYTfGzVQ0AJAzzbtg0/Q1wdYNAnhSDafygEv6kNiquk0r0RyasUUevEXs2LY3vSgKsKseI8ZZlQEMtE9/k/iAG7JNcEbVg53YTurNTrPnXJOU88mf3TToX14HpYsS1ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfolx45w0i8CdAUjjeAaYdhG9+NDHxop0UvNOqlGqYJexqPLuvX8iyUaYxNGzZxFgGI3GpKfmQP2JQWQ1E5JtY/n8iNLOKRMwqkuxSCKJxZJq4Sl/m/Yv7TS1P5LNgAj8QLCypxsWrTAmq2HSpkeSk4JBtsYxX6uhbGM/K1sEktKybVTHu22/7TmRqWTmOUy9wQvMjJb2IXdMGLG3hVntN/WWcs5w8vbt1i8Kk6o19W2MjZ95JaECKjBDYRlhG1KmSBtrsKsCBQoBzwH/rXfksTO9JoUYLXiW0IppB7DhNH4PJ5hZI91R8rR0H3/bKkLSuDaKLWSqMhozdhXsIIKvJQ==" ],
"x5t": "MnC_VZcATfM5pOYiJHMba9goEKY",
"x5t#S256": "KwGC2HYt-QOfeTjjEjYjyOc717qmTGRZBC-H_LyENyA"
}

0 comments on commit fd761c6

Please sign in to comment.