mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
8298127: HSS/LMS Signature Verification
Reviewed-by: weijun, mullan
This commit is contained in:
parent
a6109bf1ea
commit
050425b141
5 changed files with 924 additions and 5 deletions
848
src/java.base/share/classes/sun/security/provider/HSS.java
Normal file
848
src/java.base/share/classes/sun/security/provider/HSS.java
Normal file
|
@ -0,0 +1,848 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package sun.security.provider;
|
||||
|
||||
import sun.security.util.*;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
import sun.security.x509.X509Key;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implementation of the Hierarchical Signature System using the
|
||||
* Leighton-Micali Signatures (HSS/LMS) as described in RFC 8554 and
|
||||
* NIST Special publication 800-208.
|
||||
*/
|
||||
public final class HSS extends SignatureSpi {
|
||||
private HSSPublicKey pubKey;
|
||||
private ByteArrayOutputStream messageStream;
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
protected void engineSetParameter(String param, Object value) {
|
||||
throw new InvalidParameterException("No settable parameters exist for HSS/LMS");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
protected AlgorithmParameters engineGetParameter(String param) {
|
||||
throw new InvalidParameterException("No parameters exist for HSS/LMS");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitSign(PrivateKey privateKey)
|
||||
throws InvalidKeyException {
|
||||
throw new InvalidKeyException("HSS/LMS signing is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitSign(PrivateKey prk, SecureRandom sr)
|
||||
throws InvalidKeyException {
|
||||
throw new InvalidKeyException("HSS/LMS signing is not supported");
|
||||
}
|
||||
|
||||
// This will never be called because engineInitSign unconditionally
|
||||
// throws an exception
|
||||
@Override
|
||||
protected byte[] engineSign() throws SignatureException {
|
||||
throw new SignatureException("HSS/LMS signing is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInitVerify(PublicKey publicKey)
|
||||
throws InvalidKeyException {
|
||||
HSSPublicKey pub;
|
||||
if (publicKey instanceof HSSPublicKey p) {
|
||||
pub = p;
|
||||
} else {
|
||||
KeyFactoryImpl factory = new HSS.KeyFactoryImpl();
|
||||
Key pk = factory.engineTranslateKey(publicKey);
|
||||
pub = (HSSPublicKey) pk;
|
||||
}
|
||||
pubKey = pub;
|
||||
messageStream = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte data) {
|
||||
messageStream.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] data, int off, int len) {
|
||||
messageStream.write(data, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean engineVerify(byte[] signature)
|
||||
throws SignatureException {
|
||||
|
||||
boolean result = true;
|
||||
try {
|
||||
HSSSignature sig = new HSSSignature(signature, pubKey);
|
||||
LMSPublicKey lmsPubKey = pubKey.lmsPublicKey;
|
||||
for (int i = 0; i < sig.Nspk; i++) {
|
||||
byte[] keyArr = sig.pubList[i].keyArray();
|
||||
result &= LMSUtils.lmsVerify(lmsPubKey, sig.siglist[i], keyArr);
|
||||
lmsPubKey = sig.pubList[i];
|
||||
}
|
||||
|
||||
result &= LMSUtils.lmsVerify(
|
||||
lmsPubKey, sig.siglist[sig.Nspk], messageStream.toByteArray());
|
||||
} finally {
|
||||
messageStream.reset();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
protected void engineSetParameter(AlgorithmParameterSpec params)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
if (params != null) {
|
||||
throw new InvalidAlgorithmParameterException("No parameters accepted");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AlgorithmParameters engineGetParameters() {
|
||||
return null;
|
||||
}
|
||||
|
||||
static class LMSPublicKey {
|
||||
final int type;
|
||||
final int otsType;
|
||||
final LMSParams lmsParams;
|
||||
private final byte[] I;
|
||||
private final byte[] T1;
|
||||
|
||||
LMSPublicKey(byte[] keyArray, int offset, boolean checkExactLength)
|
||||
throws InvalidKeyException {
|
||||
int inLen = keyArray.length - offset;
|
||||
if (inLen < 8) {
|
||||
throw new InvalidKeyException("LMS public key is too short");
|
||||
}
|
||||
type = LMSUtils.fourBytesToInt(keyArray, offset);
|
||||
otsType = LMSUtils.fourBytesToInt(keyArray, offset + 4);
|
||||
LMOTSParams lmotsParams;
|
||||
|
||||
try {
|
||||
lmsParams = LMSParams.of(type);
|
||||
lmotsParams = LMOTSParams.of(otsType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidKeyException(e.getMessage());
|
||||
}
|
||||
|
||||
int m = lmsParams.m;
|
||||
if ((inLen < (24 + m)) || (checkExactLength && (inLen != (24 + m))) ||
|
||||
!lmsParams.hasSameHash(lmotsParams)) {
|
||||
throw new InvalidKeyException("Wrong LMS public key length");
|
||||
}
|
||||
|
||||
I = Arrays.copyOfRange(keyArray, offset + 8, offset + 8 + 16);
|
||||
T1 = Arrays.copyOfRange(keyArray, offset + 24, offset + 24 + m);
|
||||
}
|
||||
|
||||
void getI(byte[] arr, int pos) {
|
||||
System.arraycopy(I, 0, arr, pos, 16);
|
||||
}
|
||||
|
||||
boolean isT1(byte[] arr, int pos) {
|
||||
int m = lmsParams.m;
|
||||
int diff = 0;
|
||||
for (int i = 0; i < m; i++) {
|
||||
diff |= (T1[i] ^ arr[pos + i]);
|
||||
}
|
||||
return (diff == 0);
|
||||
}
|
||||
|
||||
byte[] keyArray() {
|
||||
byte[] result = new byte[keyArrayLength()];
|
||||
LMSUtils.intToFourBytes(type, result, 0);
|
||||
LMSUtils.intToFourBytes(otsType, result, 4);
|
||||
System.arraycopy(I, 0, result, 8, 16);
|
||||
System.arraycopy(T1, 0, result, 24, lmsParams.m);
|
||||
return result;
|
||||
}
|
||||
|
||||
int keyArrayLength() {
|
||||
return 24 + lmsParams.m;
|
||||
}
|
||||
}
|
||||
|
||||
static class LMSUtils {
|
||||
static final int LMS_RESERVED = 0;
|
||||
static final int LMS_SHA256_M32_H5 = 5;
|
||||
static final int LMS_SHA256_M32_H10 = 6;
|
||||
static final int LMS_SHA256_M32_H15 = 7;
|
||||
static final int LMS_SHA256_M32_H20 = 8;
|
||||
static final int LMS_SHA256_M32_H25 = 9;
|
||||
|
||||
static String lmsType(int type) {
|
||||
String typeStr;
|
||||
switch (type) {
|
||||
case LMS_RESERVED: typeStr = "LMS_RESERVED"; break;
|
||||
case LMS_SHA256_M32_H5: typeStr = "LMS_SHA256_M32_H5"; break;
|
||||
case LMS_SHA256_M32_H10: typeStr = "LMS_SHA256_M32_H10"; break;
|
||||
case LMS_SHA256_M32_H15: typeStr = "LMS_SHA256_M32_H15"; break;
|
||||
case LMS_SHA256_M32_H20: typeStr = "LMS_SHA256_M32_H20"; break;
|
||||
case LMS_SHA256_M32_H25: typeStr = "LMS_SHA256_M32_H25"; break;
|
||||
default: typeStr = "unrecognized";
|
||||
}
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
static final int LMOTS_RESERVED = 0;
|
||||
static final int LMOTS_SHA256_N32_W1 = 1;
|
||||
static final int LMOTS_SHA256_N32_W2 = 2;
|
||||
static final int LMOTS_SHA256_N32_W4 = 3;
|
||||
static final int LMOTS_SHA256_N32_W8 = 4;
|
||||
|
||||
static String lmotsType(int type) {
|
||||
String typeStr;
|
||||
switch (type) {
|
||||
case LMOTS_RESERVED: typeStr = "LMOTS_RESERVED"; break;
|
||||
case LMOTS_SHA256_N32_W1: typeStr = "LMOTS_SHA256_N32_W1"; break;
|
||||
case LMOTS_SHA256_N32_W2: typeStr = "LMOTS_SHA256_N32_W2"; break;
|
||||
case LMOTS_SHA256_N32_W4: typeStr = "LMOTS_SHA256_N32_W4"; break;
|
||||
case LMOTS_SHA256_N32_W8: typeStr = "LMOTS_SHA256_N32_W8"; break;
|
||||
default: typeStr = "unrecognized";
|
||||
}
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
|
||||
static int fourBytesToInt(byte[] arr, int i) {
|
||||
return ((arr[i] & 0xff) << 24) |
|
||||
((arr[i + 1] & 0xff) << 16) |
|
||||
((arr[i + 2] & 0xff) << 8) |
|
||||
(arr[i + 3] & 0xff);
|
||||
}
|
||||
|
||||
static void intToFourBytes(int i, byte[] arr, int pos) {
|
||||
arr[pos] = (byte) (i >> 24);
|
||||
arr[pos + 1] = (byte) (i >> 16);
|
||||
arr[pos + 2] = (byte) (i >> 8);
|
||||
arr[pos + 3] = (byte) i;
|
||||
}
|
||||
static boolean lmsVerify(
|
||||
LMSPublicKey lmsPublicKey, LMSignature sig, byte[] message)
|
||||
throws SignatureException {
|
||||
|
||||
if ((sig.sigOtsType != lmsPublicKey.otsType) ||
|
||||
(sig.sigLmType != lmsPublicKey.type)) {
|
||||
return false;
|
||||
}
|
||||
LMOTSignature lmotSig = sig.lmotSig;
|
||||
LMOTSParams lmotsParams = lmotSig.lmotsParams;
|
||||
int q = sig.q;
|
||||
int m = lmsPublicKey.lmsParams.m;
|
||||
int hashAlg_m = lmsPublicKey.lmsParams.hashAlg_m;
|
||||
int n = lmotsParams.n;
|
||||
|
||||
try {
|
||||
byte[] otsPkCandidate =
|
||||
lmotsParams.lmotsPubKeyCandidate(sig, message, lmsPublicKey);
|
||||
int nodeNum = lmsPublicKey.lmsParams.twoPowh + q;
|
||||
int tmp0MsgLen = 22 + n;
|
||||
int tmpLoopMsgLen = 22 + m + hashAlg_m;
|
||||
byte[] tmpMsg = new byte[Integer.max(tmp0MsgLen, tmpLoopMsgLen)];
|
||||
lmsPublicKey.getI(tmpMsg, 0);
|
||||
MessageDigest md =
|
||||
MessageDigest.getInstance(lmsPublicKey.lmsParams.hashAlgStr);
|
||||
LMSUtils.intToFourBytes(nodeNum, tmpMsg, 16);
|
||||
tmpMsg[20] = (byte) 0x82; // D_LEAF = 0x8282
|
||||
tmpMsg[21] = (byte) 0x82;
|
||||
System.arraycopy(otsPkCandidate, 0, tmpMsg, 22, n);
|
||||
md.update(tmpMsg, 0, tmp0MsgLen);
|
||||
if ((nodeNum & 1) == 1) {
|
||||
md.digest(tmpMsg, 22 + m, hashAlg_m);
|
||||
} else {
|
||||
md.digest(tmpMsg, 22, hashAlg_m);
|
||||
}
|
||||
tmpMsg[20] = (byte) 0x83; // D_INTR = 0x8383
|
||||
tmpMsg[21] = (byte) 0x83;
|
||||
|
||||
int i = 0;
|
||||
while (nodeNum > 1) {
|
||||
LMSUtils.intToFourBytes(nodeNum / 2, tmpMsg, 16);
|
||||
|
||||
if ((nodeNum & 1) == 1) {
|
||||
sig.getPath(i, tmpMsg, 22);
|
||||
} else {
|
||||
sig.getPath(i, tmpMsg, 22 + m);
|
||||
}
|
||||
md.update(tmpMsg, 0, 22 + 2 * m);
|
||||
nodeNum /= 2;
|
||||
if ((nodeNum & 1) == 1) {
|
||||
md.digest(tmpMsg, 22 + m, hashAlg_m);
|
||||
} else {
|
||||
md.digest(tmpMsg, 22, hashAlg_m);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return lmsPublicKey.isT1(tmpMsg, 22 + m);
|
||||
} catch (NoSuchAlgorithmException | DigestException e) {
|
||||
throw new ProviderException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class LMOTSignature {
|
||||
final int otSigType;
|
||||
final LMOTSParams lmotsParams;
|
||||
private final int n;
|
||||
private final byte[] C;
|
||||
private final byte[][] y;
|
||||
|
||||
LMOTSignature(byte[] sigArray, LMOTSParams lmotsParams)
|
||||
throws InvalidParameterException {
|
||||
int inLen = sigArray.length;
|
||||
if (inLen < 4) {
|
||||
throw new InvalidParameterException("OTS signature is too short");
|
||||
}
|
||||
otSigType = lmotsParams.lmotSigType;
|
||||
this.lmotsParams = lmotsParams;
|
||||
n = lmotsParams.n;
|
||||
int p = lmotsParams.p;
|
||||
if (inLen != (4 + n * (p + 1))) {
|
||||
throw new InvalidParameterException("OTS signature has incorrect length");
|
||||
}
|
||||
C = Arrays.copyOfRange(sigArray, 4, 4 + n);
|
||||
int pStart = 4 + n;
|
||||
y = new byte[p][n];
|
||||
for (int i = 0; i < p; i++) {
|
||||
y[i] = Arrays.copyOfRange(sigArray, pStart, pStart + n);
|
||||
pStart += n;
|
||||
}
|
||||
}
|
||||
|
||||
void getC(byte[] arr, int pos) {
|
||||
System.arraycopy(C, 0, arr, pos, n);
|
||||
}
|
||||
|
||||
void getY(int i, byte[] arr, int pos) {
|
||||
System.arraycopy(y[i], 0, arr, pos, n);
|
||||
}
|
||||
}
|
||||
|
||||
static class LMSParams {
|
||||
final int m; // the number of bytes used from the hash output
|
||||
final int hashAlg_m = 32; // output length of the LMS tree hash function
|
||||
final int h; // height of the LMS tree
|
||||
final int twoPowh;
|
||||
final String hashAlgStr;
|
||||
|
||||
LMSParams(int m, int h, String hashAlgStr) {
|
||||
this.m = m;
|
||||
this.h = h;
|
||||
this.hashAlgStr = hashAlgStr;
|
||||
twoPowh = 1 << h;
|
||||
}
|
||||
|
||||
static LMSParams of(int type) {
|
||||
int m;
|
||||
int h;
|
||||
String hashAlgStr;
|
||||
switch (type) {
|
||||
case LMSUtils.LMS_SHA256_M32_H5:
|
||||
m = 32;
|
||||
h = 5;
|
||||
hashAlgStr = "SHA-256";
|
||||
break;
|
||||
case LMSUtils.LMS_SHA256_M32_H10:
|
||||
m = 32;
|
||||
h = 10;
|
||||
hashAlgStr = "SHA-256";
|
||||
break;
|
||||
case LMSUtils.LMS_SHA256_M32_H15:
|
||||
m = 32;
|
||||
h = 15;
|
||||
hashAlgStr = "SHA-256";
|
||||
break;
|
||||
case LMSUtils.LMS_SHA256_M32_H20:
|
||||
m = 32;
|
||||
h = 20;
|
||||
hashAlgStr = "SHA-256";
|
||||
break;
|
||||
case LMSUtils.LMS_SHA256_M32_H25:
|
||||
m = 32;
|
||||
h = 25;
|
||||
hashAlgStr = "SHA-256";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported or bad LMS type");
|
||||
}
|
||||
|
||||
return new LMSParams(m, h, hashAlgStr);
|
||||
}
|
||||
|
||||
boolean hasSameHash(LMSParams other) {
|
||||
return other.hashAlgStr.equals(hashAlgStr) && (other.m == m);
|
||||
}
|
||||
|
||||
boolean hasSameHash(LMOTSParams lmotsParams) {
|
||||
return lmotsParams.hashAlgName.equals(hashAlgStr) &&
|
||||
(lmotsParams.n == m);
|
||||
}
|
||||
}
|
||||
|
||||
static class LMSignature {
|
||||
final int sigLmType;
|
||||
final int sigOtsType;
|
||||
private final byte[] qArr;
|
||||
final int q; // serial number of the LMS key being used for this signature
|
||||
final LMOTSignature lmotSig;
|
||||
final int n; // output length of the hash function used in the OTS
|
||||
final int p; // number of hash chains in the signature
|
||||
final int m; // output length of the hash function used in the Merkle tree
|
||||
final int h; // height of the Merkle tree
|
||||
private final byte[][] path;
|
||||
|
||||
LMSignature(byte[] sigArray, int offset, boolean checkExactLen)
|
||||
throws SignatureException {
|
||||
int inLen = sigArray.length - offset;
|
||||
if (inLen < 8) {
|
||||
throw new SignatureException("LMS signature is too short");
|
||||
}
|
||||
|
||||
LMOTSParams lmotsParams;
|
||||
q = LMSUtils.fourBytesToInt(sigArray, offset);
|
||||
qArr = Arrays.copyOfRange(sigArray, offset, offset + 4);
|
||||
sigOtsType = LMSUtils.fourBytesToInt(sigArray, offset + 4);
|
||||
try {
|
||||
lmotsParams = LMOTSParams.of(sigOtsType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
|
||||
n = lmotsParams.n;
|
||||
p = lmotsParams.p;
|
||||
|
||||
if (inLen < (12 + n * (p + 1))) {
|
||||
throw new SignatureException("LMS signature is too short");
|
||||
}
|
||||
|
||||
int otsSigLen = 4 + n * (p + 1);
|
||||
byte[] otSigArr = Arrays.copyOfRange(
|
||||
sigArray, offset + 4, offset + 4 + otsSigLen);
|
||||
lmotSig = new LMOTSignature(otSigArr, lmotsParams);
|
||||
|
||||
int sigTypePos = offset + 4 + otsSigLen;
|
||||
sigLmType = LMSUtils.fourBytesToInt(sigArray, sigTypePos);
|
||||
|
||||
LMSParams lmsParams;
|
||||
try {
|
||||
lmsParams = LMSParams.of(sigLmType);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SignatureException(e);
|
||||
}
|
||||
m = lmsParams.m;
|
||||
h = lmsParams.h;
|
||||
|
||||
int sigArrLen = (12 + n * (p + 1) + m * h);
|
||||
if ((q >= (1 << h)) ||
|
||||
(inLen < sigArrLen) ||
|
||||
(checkExactLen && (inLen != sigArrLen))) {
|
||||
throw new SignatureException("LMS signature length is incorrect");
|
||||
}
|
||||
|
||||
int pStart = offset + 12 + n * (p + 1);
|
||||
path = new byte[h][m];
|
||||
for (int i = 0; i < h; i++) {
|
||||
path[i] = Arrays.copyOfRange(sigArray, pStart, pStart + m);
|
||||
pStart += m;
|
||||
}
|
||||
}
|
||||
|
||||
int sigArrayLength() {
|
||||
return 12 + n * (p + 1) + m * h;
|
||||
}
|
||||
|
||||
void getQArr(byte[] arr, int pos) {
|
||||
System.arraycopy(qArr, 0, arr, pos, 4);
|
||||
}
|
||||
|
||||
void getPath(int i, byte[] arr, int pos) {
|
||||
System.arraycopy(path[i], 0, arr, pos, m);
|
||||
}
|
||||
}
|
||||
|
||||
static class LMOTSParams {
|
||||
final int lmotSigType;
|
||||
final int n; // the number of bytes used from the hash output
|
||||
final int hashAlg_n = 32; // the output length of the hash function
|
||||
final int w;
|
||||
final int twoPowWMinus1;
|
||||
final int ls;
|
||||
final int p;
|
||||
final String hashAlgName;
|
||||
|
||||
// The initial buffer image for the lmotsPubKeyCandidate() function.
|
||||
// In that function a clone of this buffer is fed into the
|
||||
// hash function as input to the implDigestFixedLengthPreprocessed()
|
||||
// function (which is basically an allocation and padding computation
|
||||
// free digest() function, so we can avoid the update()-digest()
|
||||
// sequence) which is parametrized so that the digest output is copied
|
||||
// back into the buffer. This way, we avoid memory allocations and some
|
||||
// computations that would have to be done otherwise.
|
||||
final byte[] hashBuf;
|
||||
// Precomputed block for SHA256 when the message size is 55 bytes
|
||||
// (i.e. when SHA256 is used)
|
||||
private static final byte[] hashbufSha256_32 = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, (byte) 0x80,
|
||||
0, 0, 0, 0, 0, 0, 1, (byte) 0xb8
|
||||
};
|
||||
|
||||
private LMOTSParams(
|
||||
int lmotSigType, int hLen, int w,
|
||||
int ls, int p, String hashAlgName) {
|
||||
this.lmotSigType = lmotSigType;
|
||||
this.n = hLen;
|
||||
this.w = w;
|
||||
this.ls = ls;
|
||||
this.p = p;
|
||||
twoPowWMinus1 = (1 << w) - 1;
|
||||
this.hashAlgName = hashAlgName;
|
||||
hashBuf = hashbufSha256_32;
|
||||
}
|
||||
|
||||
static LMOTSParams of(int lmotsType) {
|
||||
LMOTSParams params;
|
||||
switch (lmotsType) {
|
||||
case LMSUtils.LMOTS_SHA256_N32_W1:
|
||||
params = new LMOTSParams(
|
||||
lmotsType, 32, 1, 7, 265, "SHA-256");
|
||||
break;
|
||||
case LMSUtils.LMOTS_SHA256_N32_W2:
|
||||
params = new LMOTSParams(
|
||||
lmotsType, 32, 2, 6, 133, "SHA-256");
|
||||
break;
|
||||
case LMSUtils.LMOTS_SHA256_N32_W4:
|
||||
params = new LMOTSParams(
|
||||
lmotsType, 32, 4, 4, 67, "SHA-256");
|
||||
break;
|
||||
case LMSUtils.LMOTS_SHA256_N32_W8:
|
||||
params = new LMOTSParams(
|
||||
lmotsType, 32, 8, 0, 34, "SHA-256");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported or bad OTS Algorithm Identifier.");
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
int coef(byte[] S, int i) {
|
||||
return (twoPowWMinus1 &
|
||||
(S[i * w / 8] >> (8 - (w * (i % (8 / w)) + w))));
|
||||
}
|
||||
|
||||
private void addCksm(byte[] S) {
|
||||
int len = n;
|
||||
int sum = 0;
|
||||
int numSlices = len * 8 / w;
|
||||
for (int i = 0; i < numSlices; i++) {
|
||||
sum += twoPowWMinus1 - coef(S, i);
|
||||
}
|
||||
sum = sum << ls;
|
||||
S[len] = (byte) (sum >> 8);
|
||||
S[len + 1] = (byte) (sum & 0xff);
|
||||
}
|
||||
|
||||
void digestFixedLengthPreprocessed(
|
||||
SHA2.SHA256 sha256, byte[] input, int inLen,
|
||||
byte[] output, int outOffset, int outLen) {
|
||||
sha256.implDigestFixedLengthPreprocessed(
|
||||
input, inLen, output, outOffset, outLen);
|
||||
}
|
||||
|
||||
byte[] lmotsPubKeyCandidate(
|
||||
LMSignature lmSig, byte[] message, LMSPublicKey pKey)
|
||||
throws SignatureException {
|
||||
LMOTSignature lmOtSig = lmSig.lmotSig;
|
||||
if (lmOtSig.otSigType != pKey.otsType) {
|
||||
throw new SignatureException(
|
||||
"OTS public key type and OTS signature type do not match");
|
||||
}
|
||||
|
||||
byte[] preQ = new byte[22 + hashAlg_n];
|
||||
pKey.getI(preQ, 0);
|
||||
lmSig.getQArr(preQ, 16);
|
||||
preQ[20] = (byte) 0x81; // D_MSG = 0x8181
|
||||
preQ[21] = (byte) 0x81;
|
||||
lmOtSig.getC(preQ, 22);
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance(hashAlgName);
|
||||
} catch (NoSuchAlgorithmException e) { // This should not happen
|
||||
throw new ProviderException("Digest implementation not found", e);
|
||||
}
|
||||
byte[] result;
|
||||
try {
|
||||
md.update(preQ, 0, 22 + n);
|
||||
md.update(message);
|
||||
byte[] QWithChecksum = new byte[hashAlg_n + 2];
|
||||
md.digest(QWithChecksum, 0, hashAlg_n);
|
||||
// the MessageDigest object has now been reset
|
||||
addCksm(QWithChecksum);
|
||||
|
||||
byte[] preCandidate = new byte[22 + (p - 1) * n + hashAlg_n];
|
||||
pKey.getI(preCandidate, 0);
|
||||
lmSig.getQArr(preCandidate, 16);
|
||||
preCandidate[20] = (byte) 0x80; // D_PBLC = 0x8080
|
||||
preCandidate[21] = (byte) 0x80;
|
||||
|
||||
byte[] preZi = hashBuf.clone();
|
||||
int hashLen = hashBuf.length;
|
||||
SHA2.SHA256 sha256 = new SHA2.SHA256();
|
||||
pKey.getI(preZi, 0);
|
||||
lmSig.getQArr(preZi, 16);
|
||||
|
||||
int twoPowWMinus2 = twoPowWMinus1 - 1;
|
||||
for (int i = 0; i < p; i++) {
|
||||
int a = coef(QWithChecksum, i);
|
||||
if (a == twoPowWMinus1) {
|
||||
lmOtSig.getY(i, preCandidate, 22 + i * n);
|
||||
} else {
|
||||
preZi[20] = (byte) (i >> 8);
|
||||
preZi[21] = (byte) i;
|
||||
lmOtSig.getY(i, preZi, 23);
|
||||
}
|
||||
|
||||
for (int j = a; j < twoPowWMinus1; j++) {
|
||||
preZi[22] = (byte) j;
|
||||
if (j < twoPowWMinus2) {
|
||||
digestFixedLengthPreprocessed(
|
||||
sha256, preZi, hashLen, preZi, 23, n);
|
||||
} else {
|
||||
digestFixedLengthPreprocessed(
|
||||
sha256, preZi, hashLen, preCandidate, 22 + i * n, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
md.update(preCandidate, 0, 22 + p * n);
|
||||
|
||||
result = md.digest();
|
||||
} catch (DigestException e) { // This should not happen
|
||||
throw new ProviderException("Digest failed", e);
|
||||
}
|
||||
if (n != hashAlg_n) {
|
||||
result = Arrays.copyOfRange(result, 0, n);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyFactoryImpl extends KeyFactorySpi {
|
||||
@Override
|
||||
protected PublicKey engineGeneratePublic(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (keySpec instanceof X509EncodedKeySpec x509Spec) {
|
||||
try {
|
||||
return new HSSPublicKey(
|
||||
x509Spec.getEncoded(), true);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidKeySpecException(e);
|
||||
}
|
||||
} else if (keySpec instanceof RawKeySpec rawSpec) {
|
||||
try {
|
||||
return new HSSPublicKey(rawSpec.getKeyArr(), false);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidKeySpecException(e);
|
||||
}
|
||||
}
|
||||
throw new InvalidKeySpecException("Unrecognized KeySpec");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
throw new InvalidKeySpecException(
|
||||
"Private key generation is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
|
||||
throws InvalidKeySpecException {
|
||||
if (key == null) {
|
||||
throw new InvalidKeySpecException("key should not be null");
|
||||
}
|
||||
if (key.getFormat().equals("X.509") &&
|
||||
key.getAlgorithm().equalsIgnoreCase("HSS/LMS")) {
|
||||
if (keySpec.isAssignableFrom(X509EncodedKeySpec.class)) {
|
||||
return keySpec.cast(new X509EncodedKeySpec(key.getEncoded()));
|
||||
}
|
||||
throw new InvalidKeySpecException(
|
||||
"keySpec is not an X509EncodedKeySpec");
|
||||
}
|
||||
throw new InvalidKeySpecException("Wrong key format or key algorithm");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Key engineTranslateKey(Key key) throws InvalidKeyException {
|
||||
if (key == null) {
|
||||
throw new InvalidKeyException("key cannot be null");
|
||||
}
|
||||
PublicKey pKey;
|
||||
try {
|
||||
// Check if key originates from this factory
|
||||
if (key instanceof HSSPublicKey) {
|
||||
return key;
|
||||
}
|
||||
// Convert key to spec
|
||||
X509EncodedKeySpec x509EncodedKeySpec
|
||||
= engineGetKeySpec(key, X509EncodedKeySpec.class);
|
||||
// Create key from spec, and return it
|
||||
pKey = engineGeneratePublic(x509EncodedKeySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
return pKey;
|
||||
}
|
||||
}
|
||||
|
||||
static class HSSPublicKey extends X509Key implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 21;
|
||||
private transient int L;
|
||||
private transient LMSPublicKey lmsPublicKey;
|
||||
|
||||
HSSPublicKey(byte[] keyArray, boolean x509Encoded)
|
||||
throws InvalidKeyException {
|
||||
if (x509Encoded) {
|
||||
decode(keyArray);
|
||||
if (!KnownOIDs.HSSLMS.value().equals(algid.getOID().toString()) ||
|
||||
(algid.getParameters() != null)) {
|
||||
throw new InvalidKeyException("X509Key is not an HSS key");
|
||||
}
|
||||
} else {
|
||||
int inLen = keyArray.length;
|
||||
if (inLen < 4) {
|
||||
throw new InvalidKeyException("HSS public key too short");
|
||||
}
|
||||
L = LMSUtils.fourBytesToInt(keyArray, 0);
|
||||
lmsPublicKey =
|
||||
new LMSPublicKey(
|
||||
Arrays.copyOfRange(keyArray, 4, keyArray.length),
|
||||
0, true);
|
||||
algid = new AlgorithmId(ObjectIdentifier.of(KnownOIDs.HSSLMS));
|
||||
byte[] derEncodedKeyarray =
|
||||
new DerOutputStream()
|
||||
.putOctetString(keyArray)
|
||||
.toByteArray();
|
||||
this.setKey(new BitArray(
|
||||
8 * derEncodedKeyarray.length, derEncodedKeyarray));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
HexDumpEncoder encoder = new HexDumpEncoder();
|
||||
|
||||
return "HSS/LMS public key, number of layers: " + L +
|
||||
", LMS type: " + LMSUtils.lmsType(lmsPublicKey.type) +
|
||||
",\nOTS type: " + LMSUtils.lmotsType(lmsPublicKey.otsType) +
|
||||
", byte array representation:\n" +
|
||||
encoder.encode(getKey().toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the key. Called by X509Key.
|
||||
*/
|
||||
@Override
|
||||
protected void parseKeyBits() throws InvalidKeyException {
|
||||
byte[] keyArray = getKey().toByteArray();
|
||||
if ((keyArray[0] != DerValue.tag_OctetString) || (keyArray[1] != keyArray.length -2)) {
|
||||
throw new InvalidKeyException("Bad X509Key");
|
||||
}
|
||||
L = LMSUtils.fourBytesToInt(keyArray, 2);
|
||||
lmsPublicKey = new LMSPublicKey(keyArray, 6, true);
|
||||
}
|
||||
|
||||
@java.io.Serial
|
||||
private Object writeReplace() throws java.io.ObjectStreamException {
|
||||
return new KeyRep(KeyRep.Type.PUBLIC,
|
||||
getAlgorithm(),
|
||||
getFormat(),
|
||||
getEncoded());
|
||||
}
|
||||
|
||||
@java.io.Serial
|
||||
private void readObject(java.io.ObjectInputStream s)
|
||||
throws java.io.ObjectStreamException {
|
||||
throw new InvalidObjectException(
|
||||
"HSS public keys are not directly deserializable");
|
||||
}
|
||||
}
|
||||
|
||||
static class HSSSignature {
|
||||
private final int Nspk;
|
||||
private final LMSignature[] siglist;
|
||||
private final LMSPublicKey[] pubList;
|
||||
|
||||
HSSSignature(byte[] sigArr, HSSPublicKey pubKey)
|
||||
throws SignatureException {
|
||||
if (sigArr.length < 4) {
|
||||
throw new SignatureException("HSS signature is too short");
|
||||
}
|
||||
Nspk = LMSUtils.fourBytesToInt(sigArr, 0);
|
||||
if (Nspk + 1 != pubKey.L) {
|
||||
throw new SignatureException(
|
||||
"HSS signature and public key have different tree heights");
|
||||
}
|
||||
siglist = new LMSignature[Nspk + 1];
|
||||
pubList = new LMSPublicKey[Nspk];
|
||||
int index = 4;
|
||||
try {
|
||||
for (int i = 0; i < Nspk; i++) {
|
||||
siglist[i] = new LMSignature(sigArr, index, false);
|
||||
index += siglist[i].sigArrayLength();
|
||||
pubList[i] = new LMSPublicKey(sigArr, index, false);
|
||||
if (!pubKey
|
||||
.lmsPublicKey
|
||||
.lmsParams
|
||||
.hasSameHash(pubList[i].lmsParams)) {
|
||||
throw new SignatureException(
|
||||
"Digest algorithm in public key and Signature do not match");
|
||||
}
|
||||
index += pubList[i].keyArrayLength();
|
||||
}
|
||||
siglist[Nspk] = new LMSignature(sigArr, index, true);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new SignatureException("Invalid key in HSS signature", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -48,6 +48,7 @@ import static sun.security.provider.ByteArrayAccess.*;
|
|||
abstract class SHA2 extends DigestBase {
|
||||
|
||||
private static final int ITERATION = 64;
|
||||
private static final int BLOCKSIZE = 64;
|
||||
// Constants for each round
|
||||
private static final int[] ROUND_CONSTS = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
|
@ -81,7 +82,7 @@ abstract class SHA2 extends DigestBase {
|
|||
* Creates a new SHA object.
|
||||
*/
|
||||
SHA2(String name, int digestLength, int[] initialHashes) {
|
||||
super(name, digestLength, 64);
|
||||
super(name, digestLength, BLOCKSIZE);
|
||||
this.initialHashes = initialHashes;
|
||||
state = new int[8];
|
||||
resetHashes();
|
||||
|
@ -115,6 +116,17 @@ abstract class SHA2 extends DigestBase {
|
|||
i2bBig(state, 0, out, ofs, engineGetDigestLength());
|
||||
}
|
||||
|
||||
|
||||
protected void implDigestFixedLengthPreprocessed(
|
||||
byte[] input, int inLen, byte[] output, int outOffset, int outLen) {
|
||||
implReset();
|
||||
|
||||
for (int ofs = 0; ofs < inLen; ofs += BLOCKSIZE) {
|
||||
implCompress0(input, ofs);
|
||||
}
|
||||
i2bBig(state, 0, output, outOffset, outLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the current block to update the state variable state.
|
||||
*/
|
||||
|
@ -129,7 +141,7 @@ abstract class SHA2 extends DigestBase {
|
|||
// Checks similar to those performed by the method 'b2iBig64'
|
||||
// are sufficient for the case when the method 'implCompress0' is
|
||||
// replaced with a compiler intrinsic.
|
||||
Preconditions.checkFromIndexSize(ofs, 64, buf.length, Preconditions.AIOOBE_FORMATTER);
|
||||
Preconditions.checkFromIndexSize(ofs, BLOCKSIZE, buf.length, Preconditions.AIOOBE_FORMATTER);
|
||||
}
|
||||
|
||||
// The method 'implCompressImpl' seems not to use its parameters.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -185,6 +185,10 @@ public final class SunEntries {
|
|||
"sun.security.provider.DSA$SHA3_384withDSAinP1363Format");
|
||||
add(p, "Signature", "SHA3-512withDSAinP1363Format",
|
||||
"sun.security.provider.DSA$SHA3_512withDSAinP1363Format");
|
||||
|
||||
attrs.clear();
|
||||
attrs.put("ImplementedIn", "Software");
|
||||
addWithAlias(p, "Signature", "HSS/LMS", "sun.security.provider.HSS", attrs);
|
||||
/*
|
||||
* Key Pair Generator engines
|
||||
*/
|
||||
|
@ -214,6 +218,8 @@ public final class SunEntries {
|
|||
*/
|
||||
addWithAlias(p, "KeyFactory", "DSA",
|
||||
"sun.security.provider.DSAKeyFactory", attrs);
|
||||
addWithAlias(p, "KeyFactory", "HSS/LMS",
|
||||
"sun.security.provider.HSS$KeyFactoryImpl", attrs);
|
||||
|
||||
/*
|
||||
* Digest engines
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -239,6 +239,7 @@ public enum KnownOIDs {
|
|||
TimeStampTokenInfo("1.2.840.113549.1.9.16.1.4"),
|
||||
SigningCertificate("1.2.840.113549.1.9.16.2.12"),
|
||||
SignatureTimestampToken("1.2.840.113549.1.9.16.2.14"),
|
||||
HSSLMS("1.2.840.113549.1.9.16.3.17", "HSS/LMS"),
|
||||
CHACHA20_POLY1305("1.2.840.113549.1.9.16.3.18", "CHACHA20-POLY1305"),
|
||||
FriendlyName("1.2.840.113549.1.9.20"),
|
||||
LocalKeyID("1.2.840.113549.1.9.21"),
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.security.util;
|
||||
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* This is a KeySpec that is used to specify a key by its byte array implementation.
|
||||
* It is intended to be used in testing algorithms where the algorithm specification
|
||||
* describes the key in this form.
|
||||
*/
|
||||
public class RawKeySpec implements KeySpec {
|
||||
private final byte[] keyArr;
|
||||
/**
|
||||
* The sole constructor.
|
||||
* @param key contains the key as a byte array
|
||||
*/
|
||||
public RawKeySpec(byte[] key) {
|
||||
keyArr = key.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter function.
|
||||
* @return a copy of the key bits
|
||||
*/
|
||||
public byte[] getKeyArr() {
|
||||
return keyArr.clone();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue