Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/AssertionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.util.Base64;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;

/**
Expand Down Expand Up @@ -88,12 +91,24 @@ public String toString() {
static public class AssertionKey {
public Object key;
public AssertionKeyAlg alg = AssertionKeyAlg.NotDefined;
public JWK jwk;
public List<Base64> x5c;

public AssertionKey(AssertionKeyAlg alg, Object key) {
this.alg = alg;
this.key = key;
}

public AssertionKey withJwk(JWK jwk) {
this.jwk = jwk;
return this;
}

public AssertionKey withX5c(List<Base64> x5c) {
this.x5c = x5c;
return this;
}

public boolean isDefined() {
return alg != AssertionKeyAlg.NotDefined;
}
Expand Down
19 changes: 19 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/CryptoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ public static String getPublicKeyPEM(PublicKey publicKey) {
"\r\n-----END PUBLIC KEY-----";
}

public static String getPublicKeyJWK(PublicKey publicKey) {
if ("RSA".equals(publicKey.getAlgorithm())) {
java.security.interfaces.RSAPublicKey rsaPublicKey = (java.security.interfaces.RSAPublicKey) publicKey;
byte[] modulusBytes = rsaPublicKey.getModulus().toByteArray();
if (modulusBytes[0] == 0) {
modulusBytes = java.util.Arrays.copyOfRange(modulusBytes, 1, modulusBytes.length);
}
byte[] exponentBytes = rsaPublicKey.getPublicExponent().toByteArray();
if (exponentBytes[0] == 0) {
exponentBytes = java.util.Arrays.copyOfRange(exponentBytes, 1, exponentBytes.length);
}
String n = Base64.getUrlEncoder().withoutPadding().encodeToString(modulusBytes);
String e = Base64.getUrlEncoder().withoutPadding().encodeToString(exponentBytes);
return String.format("{\"kty\":\"RSA\",\"n\":\"%s\",\"e\":\"%s\"}", n, e);
} else {
throw new IllegalArgumentException("Unsupported public key algorithm: " + publicKey.getAlgorithm());
}
}
Comment thread
cshamrick marked this conversation as resolved.

public static String getPrivateKeyPEM(PrivateKey privateKey) {
return "-----BEGIN PRIVATE KEY-----\r\n" +
Base64.getMimeEncoder().encodeToString(privateKey.getEncoded()) +
Expand Down
70 changes: 63 additions & 7 deletions sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.util.X509CertUtils;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.opentdf.platform.sdk.SDK.AssertionException;
Expand All @@ -33,6 +36,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Base64;
Expand Down Expand Up @@ -400,7 +404,7 @@ public void sign(final HashValues hashValues, final AssertionConfig.AssertionKey
// returns the hash and the signature. It returns an error if the verification
// fails.
public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey)
throws ParseException, JOSEException {
throws ParseException, JOSEException, java.security.cert.CertificateException {
if (binding == null) {
throw new AssertionException("Binding is null in assertion", this.id);
}
Expand All @@ -409,7 +413,37 @@ public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey)
binding = null; // Clear the binding after use

SignedJWT signedJWT = SignedJWT.parse(signatureString);
JWSVerifier verifier = createVerifier(assertionKey);
JWSHeader header = signedJWT.getHeader();
JWSVerifier verifier = null;

// Check for JWK in header
if (header.getJWK() != null) {
try {
verifier = createVerifier(header.getJWK());
} catch (JOSEException e) {
throw new SDKException("Invalid JWK in JWT header", e);
}
}

// Check for X.509 certificate chain in header
Comment thread
pflynn-virtru marked this conversation as resolved.
if (verifier == null && header.getX509CertChain() != null && !header.getX509CertChain().isEmpty()) {
try {
X509Certificate cert = X509CertUtils.parse(header.getX509CertChain().get(0).decode());
if (cert.getPublicKey() instanceof RSAPublicKey) {
verifier = createVerifier((RSAPublicKey) cert.getPublicKey());
} else {
throw new SDKException("Unsupported public key type in X.509 certificate");
}
} catch (IllegalArgumentException e) {
throw new SDKException("Invalid Base64 in X.509 certificate in JWT header", e);
}
Comment thread
cshamrick marked this conversation as resolved.
}
Comment thread
pflynn-virtru marked this conversation as resolved.


if (verifier == null) {
verifier = createVerifier(assertionKey);
}


if (!signedJWT.verify(verifier)) {
throw new SDKException("Unable to verify assertion signature");
Expand All @@ -424,19 +458,27 @@ public Assertion.HashValues verify(AssertionConfig.AssertionKey assertionKey)

private SignedJWT createSignedJWT(final JWTClaimsSet claims, final AssertionConfig.AssertionKey assertionKey)
throws SDKException {
final JWSHeader jwsHeader;
final JWSHeader.Builder headerBuilder;
switch (assertionKey.alg) {
case RS256:
jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
headerBuilder = new JWSHeader.Builder(JWSAlgorithm.RS256);
break;
case HS256:
jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).build();
headerBuilder = new JWSHeader.Builder(JWSAlgorithm.HS256);
break;
default:
throw new SDKException("Unknown assertion key algorithm, error signing assertion");
}

return new SignedJWT(jwsHeader, claims);
if (assertionKey.jwk != null) {
headerBuilder.jwk(assertionKey.jwk);
}

if (assertionKey.x5c != null) {
headerBuilder.x509CertChain(assertionKey.x5c);
}

return new SignedJWT(headerBuilder.build(), claims);
}

private JWSSigner createSigner(final AssertionConfig.AssertionKey assertionKey)
Expand All @@ -460,13 +502,27 @@ private JWSSigner createSigner(final AssertionConfig.AssertionKey assertionKey)
private JWSVerifier createVerifier(AssertionConfig.AssertionKey assertionKey) throws JOSEException {
switch (assertionKey.alg) {
case RS256:
return new RSASSAVerifier((RSAPublicKey) assertionKey.key);
if (assertionKey.key instanceof JWK) {
return createVerifier((JWK) assertionKey.key);
} else if (assertionKey.key instanceof RSAPublicKey) {
return createVerifier((RSAPublicKey) assertionKey.key);
} else {
throw new SDKException("Expected JWK or RSAPublicKey for RS256 algorithm");
}
case HS256:
return new MACVerifier((byte[]) assertionKey.key);
default:
throw new SDKException("Unknown verify key, unable to verify assertion signature");
}
}

private JWSVerifier createVerifier(JWK jwk) throws JOSEException {
return new RSASSAVerifier(jwk.toRSAKey());
}
Comment thread
cshamrick marked this conversation as resolved.
Comment thread
cshamrick marked this conversation as resolved.

private JWSVerifier createVerifier(RSAPublicKey publicKey) throws JOSEException {
return new RSASSAVerifier(publicKey);
}
}

public static class AssertionValueAdapter implements JsonDeserializer<AssertionConfig.Statement> {
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ Reader loadTDF(SeekableByteChannel tdf, Config.TDFReaderConfig tdfReaderConfig)
Manifest.Assertion.HashValues hashValues;
try {
hashValues = assertion.verify(assertionKey);
} catch (ParseException | JOSEException e) {
} catch (ParseException | JOSEException | java.security.cert.CertificateException e) {
throw new SDKException("error validating assertion hash", e);
}
var hashOfAssertionAsHex = assertion.hash();
Expand Down
Loading
Loading