Yes, they do, but this is not a solution, unfortunately. Signing has to be performed on a secure server, and these megabytes of bash scripts interspersed with Python interspersed with bash again, having full root access to a host system is a huge attack surface, compared to relatively small server on a high-level language such as Java.
This becomes more problematic when you realize that these scripts don’t actualy perform some complex and elaborate tasks: you can replace whole l4t_sign_image.sh
with all tegrasign, tegraflash and tegrahost binaries by 45 lines of Java which will do the same for regular binaries.
I can even attach a concrete example to prove my words:
package some.pkg;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
public class XavSign {
private static byte[] reverseArray(byte[] x) {
byte[] r = new byte[x.length];
for (int i = 0; i < x.length; i++) r[x.length - 1 - i] = x[i];
return r;
}
private static byte[] toLittleEndian(BigInteger x, int byteLength) {
byte[] out = new byte[byteLength];
byte[] rawBytes = x.toByteArray();
for (int i = 0; i < Math.min(byteLength, rawBytes.length); i++) out[i] = rawBytes[rawBytes.length - 1 - i];
return out;
}
/**
* In-place encrypt {@code dataBuffer} with SBK and sign it with PKC, returning detached signature header.
* Function assumes that buffer is already padded to 16 bytes.
*
* @param pkc Private key for RSA signature
* @param sbk Symmetric 128-bit key for SBK encryption
* @param dataBuffer Data buffer
* @param magic 4-character magic value
* @return Detached signature header
*/
public static byte[] encryptAndSign(RSAPrivateKey pkc, byte[] sbk, ByteBuffer dataBuffer, String magic)
throws Exception
{
Cipher aes = Cipher.getInstance("AES/CBC/NoPadding");
aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sbk, "AES"), new IvParameterSpec(new byte[16]));
aes.doFinal(dataBuffer.clear(), dataBuffer.duplicate());
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(dataBuffer.clear());
byte[] dataHash = sha256.digest();
byte[] headerArray = new byte[4096];
ByteBuffer header = ByteBuffer.wrap(headerArray).order(ByteOrder.LITTLE_ENDIAN);
ByteBuffer signedHeader = ByteBuffer.wrap(headerArray, 0xb90, 0x470).slice().order(ByteOrder.LITTLE_ENDIAN);
signedHeader.put(16, (byte) 1);
for (int i = 0; i < 4; i++) signedHeader.put(32 + i, (byte) magic.charAt(i));
signedHeader.putInt(36, dataBuffer.capacity());
signedHeader.put(80, dataHash);
sha256.update(signedHeader.clear());
byte[] signedHeaderHash = sha256.digest();
header.putInt(0, 0x4144564e); // 'NVDA' signature
header.put(16, signedHeaderHash);
Signature sig = Signature.getInstance("RSASSA-PSS");
sig.setParameter(new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1));
sig.initSign(pkc);
sig.update(signedHeader.clear());
header.put(48, reverseArray(sig.sign()));
int bitLength = pkc.getModulus().bitLength();
int byteLength = bitLength / 8;
header.put(0x1f0, toLittleEndian(pkc.getModulus(), byteLength));
if (bitLength > 2048) {
// Montgomery values:
BigInteger R = BigInteger.ONE.shiftLeft(bitLength);
BigInteger N = pkc.getModulus();
BigInteger Ni = R.multiply(R.modInverse(N)).subtract(BigInteger.ONE).divide(N);
BigInteger RR = R.multiply(R).mod(N);
header.put(0x1f0 + byteLength, toLittleEndian(Ni, byteLength));
header.put(0x1f0 + 2 * byteLength, toLittleEndian(RR, byteLength));
}
return headerArray;
}
public static void main(String[] args) throws Exception {
RSAPrivateKey key = (RSAPrivateKey) CryptoUtils.parseKey(Files.readString(Paths.get("pkc.pem")));
byte[] sbk = Hex.decode(Files.readString(Paths.get("sbk.txt")));
byte[] data = Files.readAllBytes(Paths.get("eks.img"));
byte[] sigHeader = encryptAndSign(key, sbk, ByteBuffer.wrap(data), "EKSB");
try (OutputStream out = Files.newOutputStream(Paths.get("eks_sigheader.img.java_out"))) {
out.write(sigHeader);
out.write(data);
}
}
}
This snippet of code produces a byte-exact copy of images signed by original flash.sh
, with exception of a non-deterministic randomized RSASSA-PSS signature.
For security perspective it’s a total nightmare to use few megabytes of bash mess that requires root privileges instead of 45 lines of Java. Especially to use it in places that handle sensitive key material. Needless to say that this also implies deployment and maintainance difficulties - you have to use outdated VM with Ubuntu 18.04 LTS, you have to download everything with SDK Manager every time, and all that stuff.
That snippet works perfectly for same set of binaries as l4t_sign_image.sh
does. With slight modification it will also work for pre-signed binaries. The only thing that remains a bit unclear (but I didn’t yet tested whether it will affect the operation of signed binaries) is a meaning of few bytes in OEM-signed part, at offsets 0xbc4
and 0xbc8
.