feat(applet): CoseVerifier RFC 9052 verify (M2A.2)
Adds CoseVerifier.verifyCoseSign1 — single-purpose RFC 9052 COSE_Sign1
verifier for the Aliro IssuerAuth signature, used once at personalization
(M2A.3) to set CredentialStore.accessDocumentVerified.
Wire walk uses hardcoded offsets that assume the
aliro_harness.issuer.cose shape (matches RFC 9052 §3 but is not a generic
CBOR parser). TODO marker for M2B.3 to swap in StructuralCbor.elementSpan.
Extracts SECP256R1_{P,A,B,G,R} into Secp256r1Params so AliroApplet and
CoseVerifier share one copy. seed(KeyPair) handles full keypair seeding;
seedPublic(ECPublicKey) covers the verify-only case for the new verifier.
Test vector: deterministic-seeded P-256 key + literal payload, signed
once via the harness cose helpers and self-verified, hex-pasted into
CoseVerifierTest. Two cases (good signature; flipped payload byte).
Regression: 80 tests green; the 3 pre-existing GCM errors from the
jcardsim m2-volume swap (AliroCryptoTest#jcardsimSupportsAesGcm,
AliroGcmTest#{partialFinalBlock,roundTripAgainstJcardsimAEADCipher})
still error, unrelated to this change.
This commit is contained in:
@@ -193,45 +193,10 @@ public class AliroApplet extends Applet {
|
||||
'V', 'o', 'l', 'a', 't', 'i', 'l', 'e', '*', '*', '*', '*'
|
||||
};
|
||||
|
||||
// secp256r1 / NIST P-256 curve parameters per FIPS 186-4 / SEC2 §2.7.2.
|
||||
// J3R180 doesn't ship a default P-256 parameter set on its EC keys, so
|
||||
// calls into genKeyPair / setS / setW / Signature.init throw
|
||||
// CryptoException.ILLEGAL_VALUE until we seed the curve explicitly.
|
||||
private static final byte[] SECP256R1_P = {
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,0x00,0x00,0x00,0x01,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF
|
||||
};
|
||||
private static final byte[] SECP256R1_A = {
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,0x00,0x00,0x00,0x01,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFC
|
||||
};
|
||||
private static final byte[] SECP256R1_B = {
|
||||
0x5A,(byte)0xC6,0x35,(byte)0xD8,(byte)0xAA,0x3A,(byte)0x93,(byte)0xE7,
|
||||
(byte)0xB3,(byte)0xEB,(byte)0xBD,0x55,0x76,(byte)0x98,(byte)0x86,(byte)0xBC,
|
||||
0x65,0x1D,0x06,(byte)0xB0,(byte)0xCC,0x53,(byte)0xB0,(byte)0xF6,
|
||||
0x3B,(byte)0xCE,0x3C,0x3E,0x27,(byte)0xD2,0x60,0x4B
|
||||
};
|
||||
private static final byte[] SECP256R1_G = {
|
||||
0x04,
|
||||
0x6B,0x17,(byte)0xD1,(byte)0xF2,(byte)0xE1,0x2C,0x42,0x47,
|
||||
(byte)0xF8,(byte)0xBC,(byte)0xE6,(byte)0xE5,0x63,(byte)0xA4,0x40,(byte)0xF2,
|
||||
0x77,0x03,0x7D,(byte)0x81,0x2D,(byte)0xEB,0x33,(byte)0xA0,
|
||||
(byte)0xF4,(byte)0xA1,0x39,0x45,(byte)0xD8,(byte)0x98,(byte)0xC2,(byte)0x96,
|
||||
0x4F,(byte)0xE3,0x42,(byte)0xE2,(byte)0xFE,0x1A,0x7F,(byte)0x9B,
|
||||
(byte)0x8E,(byte)0xE7,(byte)0xEB,0x4A,0x7C,0x0F,(byte)0x9E,0x16,
|
||||
0x2B,(byte)0xCE,0x33,0x57,0x6B,0x31,0x5E,(byte)0xCE,
|
||||
(byte)0xCB,(byte)0xB6,0x40,0x68,0x37,(byte)0xBF,0x51,(byte)0xF5
|
||||
};
|
||||
private static final byte[] SECP256R1_R = {
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,0x00,0x00,0x00,0x00,
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
|
||||
(byte)0xBC,(byte)0xE6,(byte)0xFA,(byte)0xAD,(byte)0xA7,0x17,(byte)0x9E,(byte)0x84,
|
||||
(byte)0xF3,(byte)0xB9,(byte)0xCA,(byte)0xC2,(byte)0xFC,0x63,0x25,0x51
|
||||
};
|
||||
// secp256r1 / NIST P-256 curve parameters live in {@link Secp256r1Params}
|
||||
// — shared with {@link CoseVerifier}. J3R180 doesn't ship a default P-256
|
||||
// parameter set on its EC keys, so seedSecp256r1() must run before any
|
||||
// genKeyPair / setS / setW / Signature.init.
|
||||
/** NFC interface byte, spec §8.3.1.13. */
|
||||
private static final byte INTERFACE_BYTE_NFC = (byte) 0x5E;
|
||||
/** Table 8-13 usage constant for UD signature (spec §8.3.3.4.3). */
|
||||
@@ -317,22 +282,12 @@ public class AliroApplet extends Applet {
|
||||
/** Loads the secp256r1 / NIST P-256 curve parameters into both halves of
|
||||
* {@code kp}. Must run before any {@code genKeyPair}, {@code setS},
|
||||
* {@code setW}, or {@code Signature.init} on cards (like J3R180) that
|
||||
* don't preset domain parameters on freshly-allocated EC keys. */
|
||||
* don't preset domain parameters on freshly-allocated EC keys.
|
||||
*
|
||||
* <p>Thin wrapper over {@link Secp256r1Params#seed(KeyPair)} kept for
|
||||
* call-site readability; the byte arrays live in the shared class. */
|
||||
private static void seedSecp256r1(KeyPair kp) {
|
||||
javacard.security.ECPublicKey pub = (javacard.security.ECPublicKey) kp.getPublic();
|
||||
javacard.security.ECPrivateKey priv = (javacard.security.ECPrivateKey) kp.getPrivate();
|
||||
pub.setFieldFP(SECP256R1_P, (short) 0, (short) SECP256R1_P.length);
|
||||
pub.setA(SECP256R1_A, (short) 0, (short) SECP256R1_A.length);
|
||||
pub.setB(SECP256R1_B, (short) 0, (short) SECP256R1_B.length);
|
||||
pub.setG(SECP256R1_G, (short) 0, (short) SECP256R1_G.length);
|
||||
pub.setR(SECP256R1_R, (short) 0, (short) SECP256R1_R.length);
|
||||
pub.setK((short) 1);
|
||||
priv.setFieldFP(SECP256R1_P, (short) 0, (short) SECP256R1_P.length);
|
||||
priv.setA(SECP256R1_A, (short) 0, (short) SECP256R1_A.length);
|
||||
priv.setB(SECP256R1_B, (short) 0, (short) SECP256R1_B.length);
|
||||
priv.setG(SECP256R1_G, (short) 0, (short) SECP256R1_G.length);
|
||||
priv.setR(SECP256R1_R, (short) 0, (short) SECP256R1_R.length);
|
||||
priv.setK((short) 1);
|
||||
Secp256r1Params.seed(kp);
|
||||
}
|
||||
|
||||
// --- Diagnostic test vectors -------------------------------------------
|
||||
@@ -436,7 +391,7 @@ public class AliroApplet extends Applet {
|
||||
AliroCrypto cryptoEcdh = CryptoSingletons.getAliroCrypto();
|
||||
for (short i = 0; i < n; i++) {
|
||||
cryptoEcdh.computeEcdhSharedX(priv,
|
||||
SECP256R1_G, (short) 0,
|
||||
Secp256r1Params.SECP256R1_G, (short) 0,
|
||||
buf, DIAG_OUT_OFF);
|
||||
}
|
||||
return;
|
||||
|
||||
306
applet/src/main/java/com/dangerousthings/aliro/CoseVerifier.java
Normal file
306
applet/src/main/java/com/dangerousthings/aliro/CoseVerifier.java
Normal file
@@ -0,0 +1,306 @@
|
||||
package com.dangerousthings.aliro;
|
||||
|
||||
import javacard.framework.JCSystem;
|
||||
import javacard.framework.Util;
|
||||
import javacard.security.ECPublicKey;
|
||||
import javacard.security.KeyBuilder;
|
||||
import javacard.security.Signature;
|
||||
|
||||
/**
|
||||
* Verifies an Aliro IssuerAuth COSE_Sign1 (RFC 9052) over ECDSA-P256 +
|
||||
* SHA-256. Single use case: at personalization we verify the Access
|
||||
* Document signature ONCE, cache the verdict (M2A.1's
|
||||
* {@code accessDocumentVerified} flag), and trust thereafter.
|
||||
*
|
||||
* <p>COSE_Sign1 wire format (RFC 9052 §3):
|
||||
* <pre>
|
||||
* [ protected_bstr, unprotected_map, payload_bstr, signature_bstr ]
|
||||
* </pre>
|
||||
* Sig_structure that was signed (RFC 9052 §4.4):
|
||||
* <pre>
|
||||
* [ "Signature1", body_protected, external_aad, payload ]
|
||||
* </pre>
|
||||
* Signature is raw {@code r || s} (64 bytes for P-256), not DER —
|
||||
* the JC {@code Signature.ALG_ECDSA_SHA_256} primitive expects DER, so
|
||||
* we transcode raw → DER before {@link Signature#verify}.
|
||||
*
|
||||
* <p>For now (M2A.2) the CBOR walk uses hardcoded offsets that assume the
|
||||
* exact wire shape produced by {@code aliro_harness.issuer.cose}:
|
||||
* <ul>
|
||||
* <li>outer array(4) tag = 0x84 at byte 0
|
||||
* <li>protected_bstr is a single-byte-length bstr at byte 1
|
||||
* <li>unprotected map is map(1) with kid bstr value
|
||||
* <li>payload_bstr is either tiny (header 0x40+len) or 1-byte len (0x58 + len)
|
||||
* <li>signature_bstr is 0x58 0x40 + 64 bytes
|
||||
* </ul>
|
||||
* M2B.3 will replace these hardcoded offsets with
|
||||
* {@code StructuralCbor.elementSpan} for true structural walking.
|
||||
*
|
||||
* <p>The verifier owns a reusable {@link ECPublicKey} slot, seeded with
|
||||
* P-256 curve params at construction. Each call calls {@code setW} with
|
||||
* the caller-supplied uncompressed (0x04 || X || Y) issuer key — no
|
||||
* per-call allocation.
|
||||
*/
|
||||
final class CoseVerifier {
|
||||
|
||||
/** Maximum supported COSE_Sign1 payload length. The Aliro Access
|
||||
* Document fits comfortably under this; we size the Sig_structure
|
||||
* working buffer for it. */
|
||||
static final short MAX_PAYLOAD = (short) 512;
|
||||
|
||||
/** Maximum supported protected bstr length. Aliro IssuerAuth uses a
|
||||
* single {alg: ES256} map (3 bytes), but we cap at 32 for slack. */
|
||||
static final short MAX_PROTECTED = (short) 32;
|
||||
|
||||
/** Length of a raw P-256 ECDSA signature: r(32) || s(32). */
|
||||
private static final short RAW_SIG_LEN = (short) 64;
|
||||
|
||||
/** Length of an uncompressed SEC1 P-256 public key: 0x04 || X(32) || Y(32). */
|
||||
private static final short UNCOMP_PUB_LEN = (short) 65;
|
||||
|
||||
/**
|
||||
* Sig_structure working buffer layout:
|
||||
* <pre>
|
||||
* 0x84 array(4)
|
||||
* 0x6A tstr(10) — "Signature1"
|
||||
* 53 69 67 6E 61 "Signature1" (10 bytes)
|
||||
* 74 75 72 65 31
|
||||
* [protected_bstr CBOR header + bytes]
|
||||
* 0x40 bstr(0) — external_aad (empty)
|
||||
* [payload_bstr CBOR header + bytes]
|
||||
* </pre>
|
||||
* "Signature1" prefix is fixed; cache it.
|
||||
*/
|
||||
private static final byte[] SIG_STRUCT_PREFIX = {
|
||||
(byte) 0x84, // array(4)
|
||||
(byte) 0x6A, // tstr(10)
|
||||
(byte) 0x53, (byte) 0x69, (byte) 0x67, (byte) 0x6E, // "Sign"
|
||||
(byte) 0x61, (byte) 0x74, (byte) 0x75, (byte) 0x72, // "atur"
|
||||
(byte) 0x65, (byte) 0x31 // "e1"
|
||||
};
|
||||
|
||||
private final ECPublicKey issuerPubKey;
|
||||
private final Signature ecdsaVerifier;
|
||||
/** Transient scratch for building the Sig_structure and DER signature.
|
||||
* Sized for MAX_PAYLOAD + MAX_PROTECTED + headers + DER overhead. */
|
||||
private final byte[] scratch;
|
||||
|
||||
CoseVerifier() {
|
||||
issuerPubKey = (ECPublicKey) KeyBuilder.buildKey(
|
||||
KeyBuilder.TYPE_EC_FP_PUBLIC,
|
||||
KeyBuilder.LENGTH_EC_FP_256,
|
||||
false);
|
||||
Secp256r1Params.seedPublic(issuerPubKey);
|
||||
ecdsaVerifier = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
|
||||
// Sig_structure size = prefix(12) + prot_hdr(<=3) + prot_bytes(<=32)
|
||||
// + ext_aad(1) + pl_hdr(<=3) + pl_bytes(<=512)
|
||||
// DER sig max = 2 + 2 + 33 + 2 + 33 = 72.
|
||||
// Round up to keep arithmetic simple.
|
||||
scratch = JCSystem.makeTransientByteArray(
|
||||
(short) 768, JCSystem.CLEAR_ON_DESELECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a COSE_Sign1 IssuerAuth blob against the supplied issuer
|
||||
* public key.
|
||||
*
|
||||
* @param coseSign1 buffer holding the CBOR-encoded COSE_Sign1
|
||||
* @param coseOff offset of the outer array tag (0x84)
|
||||
* @param coseLen total length of the COSE_Sign1 blob
|
||||
* @param issuerPubUncomp buffer holding the 65-byte uncompressed SEC1
|
||||
* pubkey (0x04 || X || Y)
|
||||
* @param pubOff offset of the 0x04 tag
|
||||
* @return {@code true} iff the signature verifies; {@code false} on any
|
||||
* malformed input, mismatched key, or invalid signature.
|
||||
*/
|
||||
boolean verifyCoseSign1(
|
||||
byte[] coseSign1, short coseOff, short coseLen,
|
||||
byte[] issuerPubUncomp, short pubOff) {
|
||||
try {
|
||||
return verifyInternal(coseSign1, coseOff, coseLen, issuerPubUncomp, pubOff);
|
||||
} catch (Throwable t) {
|
||||
// Any malformed CBOR, length overrun, crypto exception, etc.
|
||||
// collapses to "not verified". The personalization caller will
|
||||
// refuse to install the access document.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyInternal(
|
||||
byte[] coseSign1, short coseOff, short coseLen,
|
||||
byte[] issuerPubUncomp, short pubOff) {
|
||||
short end = (short) (coseOff + coseLen);
|
||||
|
||||
// TODO M2B.3: replace with StructuralCbor.elementSpan — the offset
|
||||
// arithmetic below assumes the exact wire shape produced by
|
||||
// aliro_harness.issuer.cose (matches RFC 9052 §3 but is not a
|
||||
// generic CBOR parser).
|
||||
short p = coseOff;
|
||||
|
||||
// Outer array(4): only major type 4, count 4, fits in one byte.
|
||||
if ((short) (p + 1) > end) return false;
|
||||
if (coseSign1[p] != (byte) 0x84) return false;
|
||||
p++;
|
||||
|
||||
// protected_bstr: read CBOR bstr header → value off/len.
|
||||
short protHdrOff = p;
|
||||
short protLen = readBstrLen(coseSign1, p, end);
|
||||
if (protLen < 0 || protLen > MAX_PROTECTED) return false;
|
||||
short protHdrLen = bstrHeaderLen(protLen);
|
||||
short protValOff = (short) (p + protHdrLen);
|
||||
if ((short) (protValOff + protLen) > end) return false;
|
||||
p = (short) (protValOff + protLen);
|
||||
|
||||
// unprotected_map: skip — its contents (kid etc.) don't enter
|
||||
// Sig_structure. Walk the map(1) and its single key/value pair.
|
||||
// CBOR major type 5 (map). We only need to skip; assume map(1)
|
||||
// shape per IssuerAuth profile (0xA1).
|
||||
if ((short) (p + 1) > end) return false;
|
||||
if ((coseSign1[p] & (byte) 0xE0) != (byte) 0xA0) return false;
|
||||
short mapCount = (short) (coseSign1[p] & 0x1F);
|
||||
if (mapCount > 23) return false; // we don't handle long-form map headers
|
||||
p++;
|
||||
for (short i = 0; i < mapCount; i++) {
|
||||
// key: small int (kid header field 4 → 1 byte 0x04). We only
|
||||
// support single-byte keys.
|
||||
if ((short) (p + 1) > end) return false;
|
||||
p++;
|
||||
// value: bstr (kid) — read header + skip body.
|
||||
short kLen = readBstrLen(coseSign1, p, end);
|
||||
if (kLen < 0) return false;
|
||||
short kHdr = bstrHeaderLen(kLen);
|
||||
p = (short) (p + kHdr + kLen);
|
||||
if (p > end) return false;
|
||||
}
|
||||
|
||||
// payload_bstr.
|
||||
short payloadHdrOff = p;
|
||||
short payloadLen = readBstrLen(coseSign1, p, end);
|
||||
if (payloadLen < 0 || payloadLen > MAX_PAYLOAD) return false;
|
||||
short payloadHdrLen = bstrHeaderLen(payloadLen);
|
||||
short payloadValOff = (short) (p + payloadHdrLen);
|
||||
if ((short) (payloadValOff + payloadLen) > end) return false;
|
||||
p = (short) (payloadValOff + payloadLen);
|
||||
|
||||
// signature_bstr: must be raw 64-byte P-256 ECDSA (r||s).
|
||||
short sigLen = readBstrLen(coseSign1, p, end);
|
||||
if (sigLen != RAW_SIG_LEN) return false;
|
||||
short sigHdrLen = bstrHeaderLen(sigLen);
|
||||
short sigValOff = (short) (p + sigHdrLen);
|
||||
if ((short) (sigValOff + sigLen) > end) return false;
|
||||
|
||||
// Build Sig_structure into scratch.
|
||||
short s = (short) 0;
|
||||
Util.arrayCopyNonAtomic(SIG_STRUCT_PREFIX, (short) 0,
|
||||
scratch, s, (short) SIG_STRUCT_PREFIX.length);
|
||||
s += (short) SIG_STRUCT_PREFIX.length;
|
||||
// protected_bstr (re-emit header so it matches input verbatim).
|
||||
s = writeBstr(coseSign1, protValOff, protLen, scratch, s);
|
||||
// external_aad = empty bstr (h''): single byte 0x40.
|
||||
scratch[s++] = (byte) 0x40;
|
||||
// payload bstr.
|
||||
s = writeBstr(coseSign1, payloadValOff, payloadLen, scratch, s);
|
||||
|
||||
// Place the DER-encoded signature after the Sig_structure.
|
||||
short derLen = rawSigToDer(coseSign1, sigValOff, scratch, s);
|
||||
short derOff = s;
|
||||
|
||||
// Load issuer pubkey.
|
||||
issuerPubKey.setW(issuerPubUncomp, pubOff, UNCOMP_PUB_LEN);
|
||||
ecdsaVerifier.init(issuerPubKey, Signature.MODE_VERIFY);
|
||||
return ecdsaVerifier.verify(
|
||||
scratch, (short) 0, s,
|
||||
scratch, derOff, derLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a CBOR byte-string length field at {@code off}. Supports the
|
||||
* inline (len ≤ 23, major-type byte 0x40+len), 1-byte-extended
|
||||
* (0x58 + uint8), and 2-byte-extended (0x59 + uint16) encodings.
|
||||
*
|
||||
* @return the bstr value length, or -1 if not a bstr or overflow.
|
||||
*/
|
||||
private static short readBstrLen(byte[] buf, short off, short end) {
|
||||
if ((short) (off + 1) > end) return -1;
|
||||
byte b0 = buf[off];
|
||||
if ((b0 & (byte) 0xE0) != (byte) 0x40) return -1; // not bstr major type
|
||||
short shortLen = (short) (b0 & 0x1F);
|
||||
if (shortLen <= 23) return shortLen;
|
||||
if (shortLen == 24) {
|
||||
if ((short) (off + 2) > end) return -1;
|
||||
return (short) (buf[(short) (off + 1)] & 0xFF);
|
||||
}
|
||||
if (shortLen == 25) {
|
||||
if ((short) (off + 3) > end) return -1;
|
||||
short hi = (short) ((buf[(short) (off + 1)] & 0xFF) << 8);
|
||||
short lo = (short) (buf[(short) (off + 2)] & 0xFF);
|
||||
return (short) (hi | lo);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Total header byte count for a bstr of {@code len} bytes (matches
|
||||
* what {@link #readBstrLen} parses). */
|
||||
private static short bstrHeaderLen(short len) {
|
||||
if (len <= 23) return 1;
|
||||
if (len <= 0xFF) return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
/** Copies {@code len} bytes from {@code src[srcOff..]} into {@code dst}
|
||||
* prefixed by a freshly-emitted CBOR bstr header. Returns the post-write
|
||||
* offset into {@code dst}. */
|
||||
private static short writeBstr(byte[] src, short srcOff, short len,
|
||||
byte[] dst, short dstOff) {
|
||||
if (len <= 23) {
|
||||
dst[dstOff++] = (byte) (0x40 | len);
|
||||
} else if (len <= 0xFF) {
|
||||
dst[dstOff++] = (byte) 0x58;
|
||||
dst[dstOff++] = (byte) len;
|
||||
} else {
|
||||
dst[dstOff++] = (byte) 0x59;
|
||||
dst[dstOff++] = (byte) ((len >> 8) & 0xFF);
|
||||
dst[dstOff++] = (byte) (len & 0xFF);
|
||||
}
|
||||
Util.arrayCopyNonAtomic(src, srcOff, dst, dstOff, len);
|
||||
return (short) (dstOff + len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a 64-byte raw ECDSA (r||s) signature into ASN.1 DER:
|
||||
* {@code SEQUENCE { INTEGER r, INTEGER s }}. Both r and s are written
|
||||
* without leading-zero stripping, and a 0x00 is prepended if the high
|
||||
* bit of the first byte is set (to keep the INTEGER positive).
|
||||
*
|
||||
* <p>Duplicates {@code AliroApplet.rawSigToDer} verbatim — both
|
||||
* callers are private and Java Card 1.7 has no facility for a shared
|
||||
* package-level helper without a separate utility class. M2B.3 may
|
||||
* consolidate.
|
||||
*/
|
||||
private static short rawSigToDer(byte[] raw, short rawOff,
|
||||
byte[] out, short outOff) {
|
||||
boolean rPad = (raw[rawOff] & 0x80) != 0;
|
||||
boolean sPad = (raw[(short) (rawOff + 32)] & 0x80) != 0;
|
||||
short rLen = rPad ? (short) 33 : (short) 32;
|
||||
short sLen = sPad ? (short) 33 : (short) 32;
|
||||
short contentLen = (short) (2 + rLen + 2 + sLen);
|
||||
|
||||
short p = outOff;
|
||||
out[p++] = (byte) 0x30;
|
||||
out[p++] = (byte) contentLen;
|
||||
|
||||
out[p++] = (byte) 0x02;
|
||||
out[p++] = (byte) rLen;
|
||||
if (rPad) out[p++] = (byte) 0x00;
|
||||
Util.arrayCopyNonAtomic(raw, rawOff, out, p, (short) 32);
|
||||
p += 32;
|
||||
|
||||
out[p++] = (byte) 0x02;
|
||||
out[p++] = (byte) sLen;
|
||||
if (sPad) out[p++] = (byte) 0x00;
|
||||
Util.arrayCopyNonAtomic(raw, (short) (rawOff + 32), out, p, (short) 32);
|
||||
p += 32;
|
||||
|
||||
return (short) (p - outOff);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.dangerousthings.aliro;
|
||||
|
||||
/**
|
||||
* secp256r1 / NIST P-256 curve parameters per FIPS 186-4 / SEC2 §2.7.2.
|
||||
*
|
||||
* <p>Shared across {@link AliroApplet} and {@link CoseVerifier} (and any
|
||||
* future Aliro applet that needs to seed an EC key). J3R180 ships ECC keys
|
||||
* with no default domain parameters; without explicitly seeding the curve,
|
||||
* calls into {@code genKeyPair} / {@code setS} / {@code setW} /
|
||||
* {@code Signature.init} throw {@code CryptoException.ILLEGAL_VALUE}.
|
||||
*
|
||||
* <p>Package-private — only intra-package callers should depend on these.
|
||||
*/
|
||||
final class Secp256r1Params {
|
||||
|
||||
private Secp256r1Params() { }
|
||||
|
||||
static final byte[] SECP256R1_P = {
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,0x00,0x00,0x00,0x01,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF
|
||||
};
|
||||
static final byte[] SECP256R1_A = {
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,0x00,0x00,0x00,0x01,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFC
|
||||
};
|
||||
static final byte[] SECP256R1_B = {
|
||||
0x5A,(byte)0xC6,0x35,(byte)0xD8,(byte)0xAA,0x3A,(byte)0x93,(byte)0xE7,
|
||||
(byte)0xB3,(byte)0xEB,(byte)0xBD,0x55,0x76,(byte)0x98,(byte)0x86,(byte)0xBC,
|
||||
0x65,0x1D,0x06,(byte)0xB0,(byte)0xCC,0x53,(byte)0xB0,(byte)0xF6,
|
||||
0x3B,(byte)0xCE,0x3C,0x3E,0x27,(byte)0xD2,0x60,0x4B
|
||||
};
|
||||
static final byte[] SECP256R1_G = {
|
||||
0x04,
|
||||
0x6B,0x17,(byte)0xD1,(byte)0xF2,(byte)0xE1,0x2C,0x42,0x47,
|
||||
(byte)0xF8,(byte)0xBC,(byte)0xE6,(byte)0xE5,0x63,(byte)0xA4,0x40,(byte)0xF2,
|
||||
0x77,0x03,0x7D,(byte)0x81,0x2D,(byte)0xEB,0x33,(byte)0xA0,
|
||||
(byte)0xF4,(byte)0xA1,0x39,0x45,(byte)0xD8,(byte)0x98,(byte)0xC2,(byte)0x96,
|
||||
0x4F,(byte)0xE3,0x42,(byte)0xE2,(byte)0xFE,0x1A,0x7F,(byte)0x9B,
|
||||
(byte)0x8E,(byte)0xE7,(byte)0xEB,0x4A,0x7C,0x0F,(byte)0x9E,0x16,
|
||||
0x2B,(byte)0xCE,0x33,0x57,0x6B,0x31,0x5E,(byte)0xCE,
|
||||
(byte)0xCB,(byte)0xB6,0x40,0x68,0x37,(byte)0xBF,0x51,(byte)0xF5
|
||||
};
|
||||
static final byte[] SECP256R1_R = {
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,0x00,0x00,0x00,0x00,
|
||||
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
|
||||
(byte)0xBC,(byte)0xE6,(byte)0xFA,(byte)0xAD,(byte)0xA7,0x17,(byte)0x9E,(byte)0x84,
|
||||
(byte)0xF3,(byte)0xB9,(byte)0xCA,(byte)0xC2,(byte)0xFC,0x63,0x25,0x51
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads the secp256r1 / NIST P-256 curve parameters into both halves of
|
||||
* {@code kp}. Must run before any {@code genKeyPair}, {@code setS},
|
||||
* {@code setW}, or {@code Signature.init} on cards (like J3R180) that
|
||||
* don't preset domain parameters on freshly-allocated EC keys.
|
||||
*/
|
||||
static void seed(javacard.security.KeyPair kp) {
|
||||
javacard.security.ECPublicKey pub = (javacard.security.ECPublicKey) kp.getPublic();
|
||||
javacard.security.ECPrivateKey priv = (javacard.security.ECPrivateKey) kp.getPrivate();
|
||||
seedPublic(pub);
|
||||
priv.setFieldFP(SECP256R1_P, (short) 0, (short) SECP256R1_P.length);
|
||||
priv.setA(SECP256R1_A, (short) 0, (short) SECP256R1_A.length);
|
||||
priv.setB(SECP256R1_B, (short) 0, (short) SECP256R1_B.length);
|
||||
priv.setG(SECP256R1_G, (short) 0, (short) SECP256R1_G.length);
|
||||
priv.setR(SECP256R1_R, (short) 0, (short) SECP256R1_R.length);
|
||||
priv.setK((short) 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeds curve params on a standalone public key (for verify-only flows
|
||||
* like {@link CoseVerifier} that never need a matching private key).
|
||||
*/
|
||||
static void seedPublic(javacard.security.ECPublicKey pub) {
|
||||
pub.setFieldFP(SECP256R1_P, (short) 0, (short) SECP256R1_P.length);
|
||||
pub.setA(SECP256R1_A, (short) 0, (short) SECP256R1_A.length);
|
||||
pub.setB(SECP256R1_B, (short) 0, (short) SECP256R1_B.length);
|
||||
pub.setG(SECP256R1_G, (short) 0, (short) SECP256R1_G.length);
|
||||
pub.setR(SECP256R1_R, (short) 0, (short) SECP256R1_R.length);
|
||||
pub.setK((short) 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user