Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
f10d837
update discovery and mapping documentation
jainrocks Aug 6, 2025
e5cfd06
update discovery and mapping documentation
jainrocks Aug 6, 2025
bb90ef8
update discovery and mapping documentation
jainrocks Aug 6, 2025
954a485
update discovery and mapping documentation
jainrocks Aug 6, 2025
8ccdc7f
documentation changes
jainrocks Aug 6, 2025
0f113e5
documentation changes
jainrocks Aug 6, 2025
7d70341
update discovery and mapping documentation
jainrocks Aug 6, 2025
b8aa290
changes for documentation
jainrocks Aug 6, 2025
61dff24
changes for documentation
jainrocks Aug 6, 2025
c7fdbf5
changes for documentation
jainrocks Aug 6, 2025
1817747
Merge branch 'faucetsdn:master' into master
jainrocks Aug 12, 2025
397f2ca
Sequencer test for updating transitional state
jainrocks Aug 13, 2025
cd45896
changes in writeback sequence
jainrocks Aug 13, 2025
545962f
Sequencer test for updating transitional state
jainrocks Aug 13, 2025
58c24dd
Sequencer test for updating transitional state
jainrocks Aug 15, 2025
2ebe717
Sequencer test for updating transitional state
jainrocks Aug 15, 2025
66cdee0
Sequencer test for updating transitional state
jainrocks Aug 16, 2025
5120a8d
Sequencer test for updating transitional state
jainrocks Aug 17, 2025
036c44e
sequencer changes
jainrocks Aug 17, 2025
d438a13
sequencer changes
jainrocks Aug 17, 2025
3c3accc
Merge branch 'master' of github.com:jainrocks/udmi
jainrocks Aug 17, 2025
f111bfa
Sequencer test for updating transitional state
jainrocks Aug 17, 2025
9763401
Sequencer test for updating transitional state
jainrocks Aug 17, 2025
7651530
Sequencer test for updating transitional state
jainrocks Aug 17, 2025
52e8f8a
sequencer changes
jainrocks Aug 20, 2025
455e667
Sequencer test for updating transitional state
jainrocks Aug 20, 2025
38d873f
sequencer changes
jainrocks Aug 20, 2025
f6769c1
Sequencer test for updating transitional state
jainrocks Aug 20, 2025
4730cea
Sequencer test for updating transitional state
jainrocks Aug 21, 2025
a07b2a9
Sequencer test for updating transitional state
jainrocks Aug 21, 2025
98386b9
Sequencer test for updating transitional state
jainrocks Aug 21, 2025
2f58d60
sequencer changes
jainrocks Aug 21, 2025
965eb10
Sequencer test for updating transitional state
jainrocks Aug 21, 2025
e6cbb48
sequencer changes
jainrocks Aug 21, 2025
a259336
Merge branch 'master' of github.com:jainrocks/udmi
jainrocks Aug 21, 2025
54ec570
sequencer changes
jainrocks Aug 21, 2025
fa54d67
sequencer changes
jainrocks Aug 21, 2025
340c59f
sequencer changes
jainrocks Aug 23, 2025
56b19fc
sequencer changes
jainrocks Aug 23, 2025
f06413f
sequencer changes
jainrocks Aug 23, 2025
651b939
Trying to fix itemized tests
jainrocks Aug 24, 2025
c6e3bdb
Trying to fix itemized test for system_last_update
jainrocks Aug 24, 2025
69c4c11
increasing time for checking the set value during the writeback success
jainrocks Aug 24, 2025
ab05976
fixing test_mosquitto step in UDMIS local setup
jainrocks Aug 24, 2025
d6ec35c
fixing test_mosquitto step in UDMIS local setup, updating the options…
jainrocks Aug 24, 2025
6c1e51c
addressing review comments
jainrocks Aug 25, 2025
9e9b4b1
adding out files
jainrocks Aug 25, 2025
9e7e3ab
Testing updates and tweaks (#3)
grafnu Aug 26, 2025
ba49f74
avoiding updating state with intermediate state every time
jainrocks Aug 26, 2025
8270d43
fixing checkstyleMain error
jainrocks Aug 26, 2025
37caa4f
fixing checkstyleMain error
jainrocks Aug 26, 2025
53df7ae
config change detection (#4)
grafnu Aug 27, 2025
7ffdfe1
Merge remote-tracking branch 'upstream/master'
jainrocks Aug 27, 2025
2473296
addressing review comments
jainrocks Aug 27, 2025
b43e99e
addressing review comments
jainrocks Aug 27, 2025
9b077ee
addressing review comments
jainrocks Aug 27, 2025
bb3539f
adding fastWrite in test_mosquitto as it is failing most of the times
jainrocks Aug 28, 2025
99ef254
Use timeout instead of fastWrite (#5)
grafnu Aug 28, 2025
e910900
increasing timeout in bin/test_mosquitto
jainrocks Aug 28, 2025
4b180e3
trying checking the older implementation
jainrocks Aug 28, 2025
98e7b7a
trying checking the older implementation
jainrocks Aug 28, 2025
378ee26
trying checking the older implementation
jainrocks Aug 28, 2025
8e53e83
cleanup ofn intermediate state method changes
jainrocks Aug 29, 2025
5babcf4
uodating itemized out
jainrocks Aug 29, 2025
e4ff0eb
Merge branch 'faucetsdn:master' into master
jainrocks Aug 30, 2025
52a5725
public/private key validation in registrar
jainrocks Sep 12, 2025
5f469a2
public/private key validation in registrar
jainrocks Sep 12, 2025
02259c0
public/private key validation in registrar
jainrocks Sep 12, 2025
a40535d
public/private key validation in registrar
jainrocks Sep 12, 2025
7514d2d
public/private key validation in registrar
jainrocks Sep 13, 2025
39beb07
script for importing num_ids into site_model
jainrocks Sep 14, 2025
13ec5e5
script for importing num_ids into site_model
jainrocks Sep 14, 2025
4df9f26
removing the script
jainrocks Sep 15, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import com.google.daq.mqtt.util.CloudIotManager;
import com.google.daq.mqtt.util.ConfigManager;
import com.google.daq.mqtt.util.DeviceExceptionManager;
import com.google.daq.mqtt.util.KeyValidator;
import com.google.udmi.util.ErrorMap;
import com.google.udmi.util.ErrorMap.ErrorMapException;
import com.google.udmi.util.ExceptionMap;
Expand Down Expand Up @@ -191,6 +192,8 @@ class LocalDevice {
private ConfigManager config;
private final DeviceExceptionManager exceptionManager;
private final SiteModel siteModel;
// In LocalDevice.java, add with other member variables.
private final KeyValidator keyValidator = new KeyValidator();

private String deviceNumId;

Expand Down Expand Up @@ -228,6 +231,8 @@ class LocalDevice {
public void initialize() {
prepareOutDir();
ifTrueThen(deviceKind == DeviceKind.LOCAL && metadata != null, this::validateMetadata);
ifTrueThen(deviceKind == DeviceKind.LOCAL && metadata != null, this::validateKeyPair);

configure();
}

Expand Down Expand Up @@ -428,6 +433,49 @@ private Set<String> keyFiles() {
return addCertFile ? Sets.union(combined, certFile) : combined;
}

/**
* Validates that if a private key is provided, the corresponding public key
* exists and they form a valid cryptographic pair.
*/
private void validateKeyPair() {
try {
String authType = getAuthType();
if (authType == null || !(authType.startsWith("RS") || authType.startsWith("ES"))) {
return;
}

String keyAlgorithm = authType.startsWith("RS") ? "rsa" : "ec";
String pemFileName = keyAlgorithm + "_private.pem";
File privateKeyFile = new File(deviceDir, pemFileName);

if (!privateKeyFile.exists()) {
return;
}

String publicKeyFileName = getPublicKeyFile();
if (publicKeyFileName == null) {
throw new RuntimeException("Private key " + privateKeyFile.getName()
+ " found, but no public key is defined for auth_type " + authType);
}

File publicKeyFile = new File(deviceDir, publicKeyFileName);
if (!publicKeyFile.exists()) {
throw new RuntimeException("Private key " + privateKeyFile.getName()
+ " found, but corresponding public key " + publicKeyFileName + " is missing.");
}

if (!keyValidator.keysMatch(privateKeyFile, publicKeyFile, keyAlgorithm)) {
throw new RuntimeException("Key pair mismatch for " + publicKeyFile.getName()
+ " and " + privateKeyFile.getName());
}
} catch (Exception e) {
captureError(ExceptionCategory.credentials, e);
throw new RuntimeException("Aborting due to key validation failure for device "
+ deviceId, e);
}
}


private Set<String> getPrivateKeyFiles() {
if (isDeviceKeySource() || !hasAuthType()) {
return Set.of();
Expand Down
155 changes: 155 additions & 0 deletions validator/src/main/java/com/google/daq/mqtt/util/KeyValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package com.google.daq.mqtt.util;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Path;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;

/**
* A helper class to validate cryptographic key pairs.
*/
public class KeyValidator {

private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider();

static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BC_PROVIDER);
}
}

private Object readPemObject(Path path) throws IOException {
if (!path.toFile().exists()) {
throw new IOException("Key file not found: " + path);
}
try (FileReader fileReader = new FileReader(path.toFile());
PEMParser pemParser = new PEMParser(fileReader)) {
Object object = pemParser.readObject();
if (object == null) {
throw new IOException("No PEM object found in " + path);
}
return object;
}
}

private PrivateKey loadPrivateKey(Path path) throws Exception {
Object pemObject = readPemObject(path);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_PROVIDER);
PrivateKey privateKey;

try {
if (pemObject instanceof PEMKeyPair) {
privateKey = converter.getPrivateKey(((PEMKeyPair) pemObject).getPrivateKeyInfo());
} else if (pemObject instanceof PrivateKeyInfo) {
privateKey = converter.getPrivateKey((PrivateKeyInfo) pemObject);
} else if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
throw new IllegalArgumentException("Private key in " + path.getFileName()
+ " is encrypted. Please provide a decrypted key.");
} else {
throw new IllegalArgumentException("Unsupported PEM object type for private key: "
+ pemObject.getClass().getName());
}
} catch (Exception e) {
throw new Exception("Failed to convert PEM object to PrivateKey for " + path.getFileName()
+ ": " + e.getMessage(), e);
}

if (privateKey == null) {
throw new Exception("JcaPEMKeyConverter returned null private key from "
+ path.getFileName());
}
return privateKey;
}

private PublicKey loadPublicKey(Path path) throws Exception {
Object pemObject = readPemObject(path);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_PROVIDER);
PublicKey publicKey;

try {
if (pemObject instanceof SubjectPublicKeyInfo) {
publicKey = converter.getPublicKey((SubjectPublicKeyInfo) pemObject);
} else if (pemObject instanceof PEMKeyPair) {
publicKey = converter.getPublicKey(((PEMKeyPair) pemObject).getPublicKeyInfo());
} else {
throw new IllegalArgumentException("Unsupported PEM object type for public key: "
+ pemObject.getClass().getName());
}
} catch (Exception e) {
throw new Exception("Failed to convert PEM object to PublicKey for " + path.getFileName()
+ ": " + e.getMessage(), e);
}

if (publicKey == null) {
throw new Exception("JcaPEMKeyConverter returned null public key from " + path.getFileName());
}
return publicKey;
}

private boolean checkKeyPairMatch(PrivateKey privateKey, PublicKey publicKey, String keyAlgorithm)
throws Exception {
String signatureAlgorithm;
if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
signatureAlgorithm = "SHA256withRSA";
} else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
signatureAlgorithm = "SHA256withECDSA";
} else {
throw new IllegalArgumentException("Unsupported key algorithm for signature: "
+ keyAlgorithm);
}

byte[] challenge = new byte[1024];
Signature signature = Signature.getInstance(signatureAlgorithm, BC_PROVIDER);

signature.initSign(privateKey);
signature.update(challenge);
byte[] signatureInBytes = signature.sign();

signature.initVerify(publicKey);
signature.update(challenge);
boolean isValid = signature.verify(signatureInBytes);
return isValid;
}

/**
* Checks if the provided private and public key files form a valid cryptographic pair
* for the specified key algorithm.
*
* @param privateKeyFile the {@link File} object for the private key PEM file.
* @param publicKeyFile the {@link File} object for the public key PEM file.
* @param keyAlgorithm the expected key algorithm, either "RSA" or "EC" (case-insensitive).
* @return {@code true} if the keys match and are valid for the algorithm, else {@code false}.
* Errors during loading or validation also result in {@code false}.
*/
public boolean keysMatch(File privateKeyFile, File publicKeyFile, String keyAlgorithm) {
if (privateKeyFile == null || publicKeyFile == null) {
return false;
}
if (!("RSA".equalsIgnoreCase(keyAlgorithm) || "EC".equalsIgnoreCase(keyAlgorithm))) {
return false;
}
return keysMatch(privateKeyFile.toPath(), publicKeyFile.toPath(), keyAlgorithm.toUpperCase());
}

private boolean keysMatch(Path privateKeyPath, Path publicKeyPath, String keyAlgorithm) {
try {
PrivateKey privateKey = loadPrivateKey(privateKeyPath);
PublicKey publicKey = loadPublicKey(publicKeyPath);

return checkKeyPairMatch(privateKey, publicKey, keyAlgorithm);
} catch (Exception e) {
return false;
}
}
}
Loading