Skip to content

Commit

Permalink
Merge pull request #738 from seadowg/extract-signed
Browse files Browse the repository at this point in the history
Add `extract-signed`
  • Loading branch information
lognaturel authored Dec 16, 2023
2 parents 349239d + 92d923c commit f489a3e
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 79 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ dependencies {
implementation 'commons-io:commons-io:2.5'

implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.6.10'
implementation 'org.bouncycastle:bcprov-jdk18on:1.77'

compileOnly 'net.sf.kxml:kxml2:2.3.0'

Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.33</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
Expand Down
88 changes: 88 additions & 0 deletions src/main/java/org/javarosa/core/util/Base64.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.javarosa.core.util;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

// Copied from: https://github.com/brsanthu/migbase64/blob/master/src/main/java/com/migcomponents/migbase64/Base64.java
// Irrelevant after Java8
public class Base64 {

private static final char[] BASE_64_TBL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
private static final int[] IA = new int[256];
static {
Arrays.fill(IA, -1);
for (int i = 0, iS = BASE_64_TBL.length; i < iS; i++)
IA[BASE_64_TBL[i]] = i;
IA['='] = 0;
}

public static String encode(byte[] sArr) {
int sLen = sArr.length;
int sOff = 0;

if (sLen == 0)
return "";

int eLen = (sLen / 3) * 3;
int dLen = ((sLen - 1) / 3 + 1) << 2;
byte[] dArr = new byte[dLen];

for (int s = sOff, d = 0; s < sOff + eLen; ) {
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
dArr[d++] = (byte) BASE_64_TBL[(i >>> 18) & 0x3f];
dArr[d++] = (byte) BASE_64_TBL[(i >>> 12) & 0x3f];
dArr[d++] = (byte) BASE_64_TBL[(i >>> 6) & 0x3f];
dArr[d++] = (byte) BASE_64_TBL[i & 0x3f];
}

int left = sLen - eLen;
if (left > 0) {
int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0);
dArr[dLen - 4] = (byte) BASE_64_TBL[i >> 12];
dArr[dLen - 3] = (byte) BASE_64_TBL[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? (byte) BASE_64_TBL[i & 0x3f] : (byte) '=';
dArr[dLen - 1] = '=';
}

return new String(dArr, StandardCharsets.UTF_8);
}

public static byte[] decode(byte[] sArr) {
int sepCnt = 0;
for (byte b : sArr)
if (IA[b & 0xff] < 0)
sepCnt++;

if ((sArr.length - sepCnt) % 4 != 0)
return new byte[0];

int pad = 0;
for (int i = sArr.length; i > 1 && IA[sArr[--i] & 0xff] <= 0; )
if (sArr[i] == '=')
pad++;

int len = ((sArr.length - sepCnt) * 6 >> 3) - pad;

byte[] dArr = new byte[len];

for (int s = 0, d = 0; d < len; ) {
int i = 0;
for (int j = 0; j < 4; j++) {
int c = IA[sArr[s++] & 0xff];
if (c >= 0)
i |= c << (18 - j * 6);
else
j--;
}

dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len)
dArr[d++] = (byte) i;
}
}

return dArr;
}
}
50 changes: 50 additions & 0 deletions src/main/java/org/javarosa/core/util/Ed25519.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.javarosa.core.util;

import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.jetbrains.annotations.Nullable;

import java.nio.charset.StandardCharsets;

public class Ed25519 {

private static final int SIGNATURE_LENGTH = 64;

@Nullable
public static String extractSigned(byte[] contents, byte[] publicKey) {
if (contents.length < 64) {
return null;
}

byte[] signature = new byte[SIGNATURE_LENGTH];
System.arraycopy(contents, 0, signature, 0, SIGNATURE_LENGTH);

int messageLength = contents.length - SIGNATURE_LENGTH;
byte[] message = new byte[messageLength];
System.arraycopy(contents, SIGNATURE_LENGTH, message, 0, messageLength);

if (verify(publicKey, signature, message)) {
return new String(message, StandardCharsets.UTF_8);
} else {
return null;
}
}

private static boolean verify(byte[] publicKey, byte[] signature, byte[] message) {
try {
Ed25519PublicKeyParameters publicKeyParameters = new Ed25519PublicKeyParameters(publicKey, 0);
Signer signer = new Ed25519Signer();
signer.init(false, publicKeyParameters);
signer.update(message, 0, message.length);

return signer.verifySignature(signature);
} catch (ArrayIndexOutOfBoundsException e) {
// The key was too small
return false;
} catch (IllegalArgumentException e) {
// The key was invalid
return false;
}
}
}
83 changes: 4 additions & 79 deletions src/main/java/org/javarosa/xpath/expr/Encoding.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
*/
package org.javarosa.xpath.expr;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.javarosa.core.util.Base64;
import org.javarosa.xpath.XPathUnsupportedException;

/**
Expand Down Expand Up @@ -46,78 +45,14 @@ byte[] decode(byte[] bytes) {
}
},
BASE64("base64") {
// Copied from: https://github.com/brsanthu/migbase64/blob/master/src/main/java/com/migcomponents/migbase64/Base64.java
// Irrelevant after Java8
@Override
String encode(byte[] sArr) {
int sLen = sArr.length;
int sOff = 0;

if (sLen == 0)
return "";

int eLen = (sLen / 3) * 3;
int dLen = ((sLen - 1) / 3 + 1) << 2;
byte[] dArr = new byte[dLen];

for (int s = sOff, d = 0; s < sOff + eLen; ) {
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
dArr[d++] = (byte) BASE_64_TBL[(i >>> 18) & 0x3f];
dArr[d++] = (byte) BASE_64_TBL[(i >>> 12) & 0x3f];
dArr[d++] = (byte) BASE_64_TBL[(i >>> 6) & 0x3f];
dArr[d++] = (byte) BASE_64_TBL[i & 0x3f];
}

int left = sLen - eLen;
if (left > 0) {
int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0);
dArr[dLen - 4] = (byte) BASE_64_TBL[i >> 12];
dArr[dLen - 3] = (byte) BASE_64_TBL[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? (byte) BASE_64_TBL[i & 0x3f] : (byte) '=';
dArr[dLen - 1] = '=';
}

return new String(dArr, StandardCharsets.UTF_8);
return Base64.encode(sArr);
}

@Override
public byte[] decode(byte[] sArr) {
int sepCnt = 0;
for (byte b : sArr)
if (IA[b & 0xff] < 0)
sepCnt++;

if ((sArr.length - sepCnt) % 4 != 0)
return new byte[0];

int pad = 0;
for (int i = sArr.length; i > 1 && IA[sArr[--i] & 0xff] <= 0; )
if (sArr[i] == '=')
pad++;

int len = ((sArr.length - sepCnt) * 6 >> 3) - pad;

byte[] dArr = new byte[len];

for (int s = 0, d = 0; d < len; ) {
int i = 0;
for (int j = 0; j < 4; j++) {
int c = IA[sArr[s++] & 0xff];
if (c >= 0)
i |= c << (18 - j * 6);
else
j--;
}

dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len)
dArr[d++] = (byte) i;
}
}

return dArr;
byte[] decode(byte[] sArr) {
return Base64.decode(sArr);
}
};

Expand All @@ -137,16 +72,6 @@ static Encoding from(String name) {

private static final char[] HEX_TBL = "0123456789abcdef".toCharArray();

private static final char[] BASE_64_TBL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();

private static final int[] IA = new int[256];
static {
Arrays.fill(IA, -1);
for (int i = 0, iS = BASE_64_TBL.length; i < iS; i++)
IA[BASE_64_TBL[i]] = i;
IA['='] = 0;
}

abstract String encode(byte[] bytes);

abstract byte[] decode(byte[] bytes);
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.utils.DateUtils;
import org.javarosa.core.services.PropertyManager;
import org.javarosa.core.util.Base64;
import org.javarosa.core.util.Ed25519;
import org.javarosa.core.util.GeoUtils;
import org.javarosa.core.util.MathUtils;
import org.javarosa.core.util.PropertyUtils;
Expand Down Expand Up @@ -496,6 +498,9 @@ public Object eval(DataInstance model, EvaluationContext evalContext) {
} else if (name.equals("base64-decode")) {
assertArgsCount(name, args, 1);
return base64Decode(argVals[0]);
} else if (name.equals("extract-signed")) {
assertArgsCount(name, args, 2);
return extractSigned(argVals[0], argVals[1]);
} else {
//check for custom handler
IFunctionHandler handler = funcHandlers.get(name);
Expand Down Expand Up @@ -1264,6 +1269,18 @@ private static String base64Decode(Object o1) {
return new String(decoded, StandardCharsets.UTF_8);
}

private static String extractSigned(Object o1, Object o2) {
byte[] decodedContents = Base64.decode(toString(o1).getBytes());
byte[] decodedPublicKey = Base64.decode(toString(o2).getBytes());

String extracted = Ed25519.extractSigned(decodedContents, decodedPublicKey);
if (extracted != null) {
return extracted;
} else {
return "";
}
}

private static Object[] subsetArgList(Object[] args, int start) {
return subsetArgList(args, start, 1);
}
Expand Down
Loading

0 comments on commit f489a3e

Please sign in to comment.