I have compiled my .proto file using the protobuf compiler and received a selection of Java files. I received a proto.java file and a .java file for each item in the .proto file, including the message type and each RPC call e.g. publicKeyRequest.java and Quote.java as the RPC and request parameter type.
Is this all the files that are needed as I still cannot seem to to get any simple response back from the server?
I want to generate a request for the PublicKeyRequest RPC call. I generated the request object, but I do not know how to actually send it via the channel.
This is the full .proto file:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.decryptiondevice";
option java_outer_classname = "DecryptionDeviceProto";
package decryptiondevice;
service DecryptionDevice {
// Decryption Request RPC
//
// Request contains ciphertext and proof
// Returns the plaintext record
rpc DecryptRecord(DecryptionRequest) returns (Record) {}
// Get Signed Root Tree Hash RPC
//
// Caller provides a nonce
// Returns a signed RTH and nonce
rpc GetRootTreeHash(RootTreeHashRequest) returns (RootTreeHash) {}
// Get Public key RPC
//
// Returns a Remote attestation report containing the public key as user data
rpc GetPublicKey(PublicKeyRequest) returns (Quote) {}
}
// Decryption Request
// - Byte array containing ciphertext
// - Proofs represented as JSON trees
message DecryptionRequest {
bytes ciphertext = 1;
string proofOfPresence = 2;
string proofOfExtension = 3;
}
// A plaintext record
message Record {
bytes plaintext = 1;
}
// RTH request contains
// - A random nonce
message RootTreeHashRequest {
bytes nonce = 1;
}
// Root Tree Hash
// Random nonce used as message ID
// Signature over rth and nonce
message RootTreeHash {
bytes rth = 1;
bytes nonce = 2;
bytes sig = 3;
}
// Public key request message
message PublicKeyRequest {
bytes nonce = 1;
}
// Attestation Quote, containing the public key
message Quote {
string quote = 1; //some format.. to be defined later
//PEM formatted key
bytes RSA_EncryptionKey = 2;
bytes RSA_VerificationKey = 3;
}
And this is the code I am attempting to run on the client side:
public static void main(String[] args) {
DeviceClient client = new DeviceClient("localhost", 50051);
MannagedChanel channel = ManagedChannelBuilder.forAddress("localhost", 50051).usePlaintext(true);
ByteString nonce = ByteString.copyFromUtf8("someRandomString");
PublicKeyRequest keyRequest = PublicKeyRequest.newBuilder().setNonce(nonce).build();
// Here I want to send this to the server
ByteString response = DecryptionDeviceProto.getKey(keyRequest, channel);//this line is not even close to being valid, but this is the sort thing I wish to achieve
Sys.out.println(response);
}
Apologies if this is very wrong, I am new to gRPC.
A few points about this system:
A client and server has already been written in Go which has been tested and works with this same .proto file.
I am attempting to rewrite the client in Java to communicate with the same server.
There are two sets of files that need to be generated: Java Protobuf and Java gRPC. To my knowledge, for all languages except Go, these are two separate generation steps (that can be combined into one protoc invocation, but they are conceptually separate).
It seems you are generating the Java Protobuf code, but not the Java gRPC code. You need to use the protoc-gen-grpc-java plugin to protoc. If you are using Maven or Gradle, read grpc-java's README. If you are running protoc manually, you can download a pre-built binary from Maven Central and see an answer to a similar question.
Related
I am trying to connect my Metamask wallet to my Java Spring-Boot backend. I was trying to follow the example here. I am able to autogenerate the nonce and receive the wallet ID without a problem. I am trying to verify the signed nonce from the Wallet on the server to make sure that the sender is indeed who they say they are. However, I am unable to find any documentation on Web3J to do this.
Is web3j not the right package to use for this? The example shows how to do the verification on NodeJS based on javascript but I don't find any example on how to do this on Java.
My understanding is that the public key is the wallet ID itself and that the message is the nonce signed by the private key of the wallet which is not shared for obvious reasons. According to this, I would need to "decrypt" the message using the public key and see if the decrypted message is same as the nonce that the backend sent to Metamask to sign. Is this correct?
Here is my code to create and send the nonce to UI:
public User findUserByPublicAddress(String publicWalletId) {
User u = userRepository.findByPublicWalletId(publicWalletId);
if(u == null) {
u = new User("", "", "", null, publicWalletId, "");
String nonce = StringUtil.generateRandomAlphaNumericString();
u.setNonce(nonce);
userRepository.saveAndFlush(u);
}
return u;
}
Here, I see if the user is already in my system and if they are not, then I just create a temporary user with a random nonce generated and saved in the DB. This nonce is sent to the UI for Metamask to sign. However, I am not sure how to do the verification part of it.
I was able to figure this out finally. My initial understanding was incorrect. I was not supposed to attempt to decrypt the message to retrieve the nonce. Rather I needed to use the nonce to see if I can retrieve the public key of the private key used to sign the message and see if that public key retrieved matches the wallet ID.
The algorithm:
Receive the signed message and the wallet ID from the client
Retrieve the nonce sent to the client with the same wallet ID
Generate the hash of the nonce
Generate the signature data from the message. This basically retrieves the V, R and S and. R and S are the outputs of the ECDSA Signature and V is the Recovery ID.
Using the ECDSA Signature and Hash of the Nonce, generate the possible public Key that was used to sign the message. At max, one will be able to generate 4 possible public keys for this message.
Check if any of the generated keys match public wallet ID that the client sent. If it matches, then we have a positive match. Generate the JWT and respond to the client. If not, we know that the nonce was not signed by the Metamask wallet we expected.
The Code:
Here is a sample code for UI (JavaScript and HTML):
web3.eth.sign(
web3.utils.sha3(nonce),
window.userWalletAddress)
.then((message) => {
console.log(message)
data['message'] = message // BODY
var xmlReq = new XMLHttpRequest();
xmlReq.onreadystatechange = function() {
if(this.readyState == 4 && this.status == 200) {
response = this.responseText
console.log(response)
}
};
xmlReq.open("POST", "/api/users/login", true)
xmlReq.setRequestHeader('Content-Type', 'application/json')
xmlReq.send(JSON.stringify(data))
})
The web3.eth.sign() takes the message to be signed and takes the wallet ID that is signing it. This is then sent to the backend. In the backend:
public User signin(UserLoginDTO loginDetails, HttpServletResponse response) {
try {
// Get the wallet ID and signed message from the body stored in the DTO
String publicWalletId = loginDetails.getPublicWalletId();
String message = loginDetails.getMessage();
// Find the nonce from the DB that was used to sign this message
User user = userRepository.findByPublicWalletId(publicWalletId);
String nonce = user.getNonce();
// Generate the HASH of the Nonce
byte[] nonceHash = Hash.sha3(nonce.getBytes()) // org.web3j.crypto.Hash
// Generate the Signature Data
byte[] signatureBytes = Numeric.hexStringToByteArray(message); // org.web3j.utils.Numeric
byte v = (byte) ((signatureBytes[64] < 27) ? (signatureBytes[64] + 27) : signatureBytes[64]);
byte[] r = Arrays.copyOfRange(signatureBytes, 0, 32);
byte[] s = Arrays.copyOfRange(signatureBytes, 32, 64);
SignatureData signatureData = new SignatureData(v, r, s); // org.web3j.crypto.Sign.SignatureData
// Generate the 4 possible Public Keys
List<String> recoveredKeys = new ArrayList<>();
for(int i = 0; i < 4; i++) {
BigInteger r = new BigInteger(1, signatureData.getR());
BigInteger s = new BigInteger(1, signatureData.getS());
ECDSASignature ecdsaSignature = new ECDSASignature(r, s);
BigInteger recoveredKey = Sign.recoverFromSignature((byte)i, ecdsaSignature, nonceHash);
if(recoveredKey != null) {
recoveredKeys.add("0x" + Keys.getAddressFromKey(recoveredKey)); // org.web3j.crypto.Keys
}
}
// Check if one of the generated Keys match the public wallet ID.
for(String recoveredKey : recoveredKeys) {
if(recoveredKey.equalsIgnoreCase(publicWalletId)) {
// Add Code here to create the JWT and add that to your HttpServletResponse. Not shown here.
return user;
}
}
throw new CustomException("Message Sign Invalid", HttpStatus.UNAUTHORIZED);
}
catch (Exception ex) {
// Custom Error Handling.
}
}
I'm trying to authenticate a webhook from starling bank on a PHP 7.0.22 (Apache/2.4.6 (Red Hat Enterprise Linux)) server.
I've been told by support that the following java code is being used to generate the digest
private String calculateSignature(String sharedSecret, String requestJson) {
try {
String contentToDigest = sharedSecret + requestJson;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
byte[] digest = messageDigest.digest(contentToDigest.getBytes());
return Base64.getEncoder().encodeToString(digest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error calculating digest for payload [" + requestJson + "]", e);
}
}
The sharedSecret I already have and the requestJson I take from the webhook POST using:
$requestJson=file_get_contents('php://input') ;
my php code to generate the hash is as follows:
$concatenated_string=$sharedSecret . json_encode($requestJson) ;
$generated_hash=base64_encode(hash('sha512', $concatenated_string ));
This doesn't give the same hash. Whilst hacking to try and find an answer, I've also tried the following :
$concatenated_string=$sharedSecret . $requestJson ;
and different hash types and options:
$generated_hash=base64_encode(hash('sha512', $concatenated_string, true ))
$generated_hash=base64_encode(openssl_digest($concatenated_string, 'sha512')) ;
base64_encode and hash are effectively doing the same thing in this case:
https://stackoverflow.com/a/11195855/3323777
You should specify third argument as TRUE at your php code to match the java version:
raw_output - Setting to TRUE will return as raw output data, otherwise the return value is binhex encoded.
http://php.net/manual/ru/function.openssl-digest.php
I've ran your both snippets on java and php and found not difference when encoding a string "test". I advise you to output the json payloads to two files on both environments and use diff to compare them.
I am trying to connect with a python server (from my colleague), with java. The aim (for now) is to send a json array. We start by sending the length first. It works with an equivalent python client, which I am trying to translate into python.
This is an excerpt from my java code
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
long length = (long) arraytosend.length();
out.print(length);
String arraytosend = new JSONArray(test2).toString();
out.println(new JSONArray(test2).toString());
The python server first reads the length like this (I just copied the relevant commands de):
N_BYTES_MSG_LEN = 8
raw_len = connection.recv(N_BYTES_MSG_LEN)
# here it output 51 as raw_len
try:
msg_len = struct.unpack(MSG_LEN_TYPE, raw_len)[0]
print msg_len
logger.debug('announced message length: {}'.format(msg_len))
except:
logger.warning('could not interpret message length')
return None
# read the incoming message
raw_data = connection.recv(msg_len)
if len(raw_data) < msg_len:
logger.info('lost connection')
return None
After the "51" it immediately goes to lost connection.
The python client code (which I am trying to translate into java), works like this:
try:
raw_data = json.dumps(dict(data=data))
except:
logger.warning('Failed to create a json representation of the data')
return False
# TODO: this could fail for *very* large objects
raw_len = struct.pack('Q', len(raw_data))
try:
connection.sendall(raw_len)
connection.sendall(raw_data)
except Exception:
logger.warning('lost connection while sending data')
raise
Your receiver is assuming the length is expressed in 8 bytes (N_BYTES_MSG_LEN). But you send the long as string. PrintWriter.write(long) is the same as PrintWriter.write(Long.valueof(long).toString). For example if the length is 356 it sends "356". You should lef pad your length first: "00000356".
I found the solution, you have to take into account that java uses. You can do this by changing the server (python) code to:
raw_len = struct.pack('!Q', len(raw_data))
And you can then send it with:
JSONObject request = new JSONObject();
request.append("data", array);
byte[] outBytes = jsonObject.toString().getBytes("UTF-8");
out.writeLong(outBytes.length);
I tried this code to send and receive an Integer with ASN.1 generated classes
Client sending an Integer:
ClientFirstRequest h = new ClientFirstRequest();
h.clientInt.setValue(9);
BerOutputStream bos = new BerOutputStream(_socket.getOutputStream());
h.encode(bos);
Server receiving it:
ClientFirstRequest h = new ClientFirstRequest();
BerInputStream in = new BerInputStream(socket.getInputStream());
h.decode(in);
ASN1Integer ClientNumber= h.clientInt;
int clientNumbervalue = (int)ClientNumber.getValue();
It work perfectly, but in the second sequence I have to send two argument, an Int and a String
Server sending an Integer and a String
ServerFirstResponse response1 = new ServerFirstResponse();
response1.serverInt.setValue(clientNumbervalue);
response1.serverString.setValue(randomString);
BerOutputStream bos = new BerOutputStream(socket.getOutputStream());
h.encode(bos);
Client receiving them
ServerFirstResponse response1 = new ServerFirstResponse();
BerInputStream in = new BerInputStream(_socket.getInputStream());
response1.decode(in);
But I got an error
com.chaosinmotion.asn1.AsnFatalException:
In decoding process, one of the elements of your SEQUENCE (or an element of an inner sequnce/set) is not OPTIONAL and not initialized!
(If exists)name of this element is : serverString at
com.turkcelltech.jac.Sequence.check_OptionalAndInitialized_Status(Sequence.java:259)
at
com.turkcelltech.jac.Sequence.fillSequenceVariables(Sequence.java:246)
at com.turkcelltech.jac.Sequence.decode(Sequence.java:105) at
Client.main(Client.java:54)
Please contact the vendor of the ASN.1 Tool you are using. They should be better able to how to handle errors in use of their ASN.1 Tool. Each ASN.1 vendor writes code differently even though the end result should be the same encoded stream of bytes regardless of which tool you are using. Note that you have not indicated here which ASN.1 Tool you are using.
Hi I have made a small example to show my problem. Here is my web-service:
package service;
import javax.jws.WebMethod;
import javax.jws.WebService;
#WebService
public class BytesService {
#WebMethod
public String redirectString(String string){
return string+" - is what you sended";
}
#WebMethod
public byte[] redirectBytes(byte[] bytes) {
System.out.println("### redirectBytes");
System.out.println("### bytes lenght:" + bytes.length);
System.out.println("### message" + new String(bytes));
return bytes;
}
#WebMethod
public byte[] genBytes() {
byte[] bytes = "Hello".getBytes();
return bytes;
}
}
I pack it in jar file and store in "axis2-1.5.1/repository/servicejars" folder. Then I generate client Proxy using Eclipse for EE default utils. And use it in my code in the following way:
BytesService service = new BytesServiceProxy();
System.out.println("Redirect string");
System.out.println(service.redirectString("Hello"));
System.out.println("Redirect bytes");
byte[] param = { (byte)21, (byte)22, (byte)23 };
System.out.println(param.length);
param = service.redirectBytes(param);
System.out.println(param.length);
System.out.println("Gen bytes");
param = service.genBytes();
System.out.println(param.length);
And here is what my client prints:
Redirect string
Hello - is what you sended
Redirect bytes
3
0
Gen bytes
5
And on server I have:
### redirectBytes
### bytes lenght:0
### message
So byte array can normally be transfered from service, but is not accepted from the client. And it works fine with strings. Now I use Base64Encoder, but I dislike this solution.
I suspect the problem is in serializing the byte-array into an XML SOAP message. The XML tag in the originating SOAP message is possibly zero bytes. I'd recommend using the SOAP monitor to take a peek at the message being sent to your web service.
You may dislike the idea of encoding the byte-arry in transit, however, you need to consider it binary data. For example what if someone sent you a SOAP message encoded in something other than UTF-8? You'll want to avoid the possibility of the byte-array data changing (conversion between character sets) when the SOAP message is parsed.