55 lines
3.6 KiB
Java
55 lines
3.6 KiB
Java
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.*;
|
|
import java.security.*;
|
|
import java.security.spec.*;
|
|
import java.util.*;
|
|
|
|
/** Local Ed25519 ZBS signing; private key material is never printed or packaged. */
|
|
public class SignZBS {
|
|
public static void main(String[] args) throws Exception {
|
|
if(args.length<3) throw new IllegalArgumentException("init <key-directory> <SteamID64> | sign <key-directory> <jar>");
|
|
Path dir=Path.of(args[1]);
|
|
Path privatePath=dir.resolve("ed25519-private.der"), publicPath=dir.resolve("ed25519-public.der");
|
|
Path authorPath=dir.resolve("author.properties");
|
|
if(args[0].equals("init")) {
|
|
if(!args[2].matches("[0-9]{17}")) throw new IllegalArgumentException("Invalid SteamID64");
|
|
Files.createDirectories(dir);
|
|
if(Files.exists(privatePath)||Files.exists(publicPath)||Files.exists(authorPath))
|
|
throw new IllegalStateException("Signing identity already exists; reuse it rather than replacing it.");
|
|
KeyPair pair=KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
|
|
Files.write(privatePath,pair.getPrivate().getEncoded(),StandardOpenOption.CREATE_NEW);
|
|
Files.write(publicPath,pair.getPublic().getEncoded(),StandardOpenOption.CREATE_NEW);
|
|
Files.writeString(authorPath,"steamID64="+args[2]+"\n",StandardOpenOption.CREATE_NEW);
|
|
Files.writeString(dir.resolve("public-profile-key.txt"),profileKey(pair.getPublic())+"\n",StandardOpenOption.CREATE_NEW);
|
|
System.out.println("Created dedicated Ed25519 ZombieBuddy signing identity for SteamID64 "+args[2]+".");
|
|
return;
|
|
}
|
|
if(!args[0].equals("sign")) throw new IllegalArgumentException("Unknown operation");
|
|
Properties author=new Properties();
|
|
try(var reader=Files.newBufferedReader(authorPath)) {author.load(reader);}
|
|
String sid=author.getProperty("steamID64");
|
|
if(sid==null||!sid.matches("[0-9]{17}")) throw new IllegalStateException("Invalid signing identity");
|
|
var factory=KeyFactory.getInstance("Ed25519");
|
|
var privateKey=factory.generatePrivate(new PKCS8EncodedKeySpec(Files.readAllBytes(privatePath)));
|
|
var publicKey=factory.generatePublic(new X509EncodedKeySpec(Files.readAllBytes(publicPath)));
|
|
Path jar=Path.of(args[2]);
|
|
String sha=HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(jar)));
|
|
byte[] payload=("ZBS:"+sid+":"+sha).getBytes(StandardCharsets.UTF_8);
|
|
var signer=Signature.getInstance("Ed25519");signer.initSign(privateKey);signer.update(payload);
|
|
byte[] signature=signer.sign();
|
|
var verifier=Signature.getInstance("Ed25519");verifier.initVerify(publicKey);verifier.update(payload);
|
|
if(!verifier.verify(signature)) throw new IllegalStateException("Signing key pair does not match");
|
|
Path sidecar=Path.of(jar.toString()+".zbs");
|
|
Files.writeString(sidecar,"ZBS\nSteamID64:"+sid+"\nSignature:"+HexFormat.of().formatHex(signature)+"\n");
|
|
System.out.println("Signed and cryptographically verified "+jar.getFileName()+" (SteamID64 "+sid+").");
|
|
System.out.println("SHA-256: "+sha);
|
|
}
|
|
private static String profileKey(PublicKey key) {
|
|
byte[] encoded=key.getEncoded();
|
|
byte[] prefix=HexFormat.of().parseHex("302a300506032b6570032100");
|
|
if(encoded.length!=44||!Arrays.equals(prefix,Arrays.copyOf(encoded,12)))
|
|
throw new IllegalStateException("Unexpected Ed25519 public key encoding");
|
|
return "JavaModZBS:"+HexFormat.of().formatHex(Arrays.copyOfRange(encoded,12,44));
|
|
}
|
|
}
|