/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package COSE; import java.util.ArrayList; import java.util.Arrays; /** * * @author Jim */ public class ASN1 { /** * This class is used internal to the ASN.1 decoding functions. * After decoding there will be one of these for each item in the * original in the encoded byte array */ public static class TagValue { public int tag; public byte[] value; public ArrayList list; public TagValue(int tagIn, byte[] valueIn) { tag = tagIn; value = valueIn; } public TagValue(int tagIn, ArrayList listIn) { tag = tagIn; list = listIn; } @Override public String toString() { String t = tagToString(tag); if(list != null) { return "TagValue{tag=" + t + ", size = " + list.size() + "}"; } else { return "TagValue{tag=" + t + ", value=" + bytesToHex(value) + '}'; } } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for(byte b: bytes) sb.append(String.format("%02x", b & 0xff)); return sb.toString(); } private static String tagToString(int tag) { switch (tag) { case 0x2: return "INTEGER"; case 0x3: return "BITSTRING"; case 0x4: return "OCTETSTRING"; case 0x5: return "NULL"; case 0x6: return "OBJECTIDENTIFIER"; case 0x30: return "SEQUENCE"; default: return Integer.toString(tag); } } } // 1.2.840.10045.3.1.7 public static final byte[] Oid_secp256r1 = new byte[]{0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x03, 0x01, 0x07}; // 1.3.132.0.34 public static final byte[] Oid_secp384r1 = new byte[]{0x06, 0x05, 0x2B, (byte) 0x81, 0x04, 0x00, 0x22}; // 1.3.132.0.35 public static final byte[] Oid_secp521r1 = new byte[]{0x06, 0x05, 0x2B, (byte) 0x81, 0x04, 0x00, 0x23}; // 1.2.840.10045.2.1 public static final byte[] oid_ecPublicKey = new byte[]{0x06, 0x07, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x2, 0x1}; // 1.3.101.110 public static final byte[] Oid_X25519 = new byte[]{0x6, 3, 0x2b, 101, 110}; // 1.3.101.111 public static final byte[] Oid_X448 = new byte[]{0x6, 3, 0x2b, 101, 111}; // 1.3.101.112 public static final byte[] Oid_Ed25519 = new byte[]{0x6, 0x3, 0x2b, 101, 112}; // 1.3.101.113 public static final byte[] Oid_Ed448 = new byte[]{0x6, 0x3, 0x2b, 101, 113}; // 1.2.840.113549.1.1.1 public static final byte[] Oid_rsaEncryption = new byte[]{0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01}; private static final byte[] SequenceTag = new byte[]{0x30}; private static final byte[] OctetStringTag = new byte[]{0x4}; private static final byte[] BitStringTag = new byte[]{0x3}; private static final int IntegerTag = 2; /** * Encode a subject public key info structure from an OID and the data bytes * for the key * This function assumes that we are encoding an EC Public key.d * * @param algorithm - encoded Object Identifier * @param keyBytes - encoded key bytes * @return - encoded SPKI * @throws CoseException - ASN encoding error. */ public static byte[] EncodeSubjectPublicKeyInfo(byte[] algorithm, byte[] keyBytes) throws CoseException { // SPKI ::= SEQUENCE { // algorithm SEQUENCE { // oid = id-ecPublicKey {1 2 840 10045 2} // namedCurve = oid for algorithm // } // subjectPublicKey BIT STRING CONTAINS key bytes // } try { ArrayList xxx = new ArrayList(); xxx.add(algorithm); xxx.add(new byte[]{3}); xxx.add(ComputeLength(keyBytes.length+1)); xxx.add(new byte[]{0}); xxx.add(keyBytes); return Sequence(xxx); } catch (ArrayIndexOutOfBoundsException e) { System.out.print(e.toString()); throw e; } } /** * Encode an EC Private key * @param oid - curve to use * @param keyBytes - bytes of the key * @param spki - optional SPKI * @return encoded private key * @throws CoseException - from lower level */ public static byte[] EncodeEcPrivateKey(byte[] oid, byte[] keyBytes, byte[] spki) throws CoseException { // ECPrivateKey ::= SEQUENCE { // version INTEGER {1} // privateKey OCTET STRING // parameters [0] OBJECT IDENTIFIER = named curve // public key [1] BIT STRING OPTIONAL // } // ArrayList xxx = new ArrayList(); xxx.add(new byte[]{2, 1, 1}); xxx.add(OctetStringTag); xxx.add(ComputeLength(keyBytes.length)); xxx.add(keyBytes); xxx.add(new byte[]{(byte)0xa0}); xxx.add(ComputeLength(oid.length)); xxx.add(oid); if (spki != null) { xxx.add(new byte[]{(byte)0xa1}); xxx.add(ComputeLength(spki.length+1)); xxx.add(new byte[]{0}); xxx.add(spki); } byte[] ecPrivateKey = Sequence(xxx); return ecPrivateKey; } /* * Decode an object which is supposed to be a SubjectPublicKeyInfo strucuture * and check that the right set of fields are in the right place * * @param encoding encoded byte string to decode * @return decoded structure * @throws CoseException */ public static ArrayList DecodeSubjectPublicKeyInfo(byte[] encoding) throws CoseException { TagValue spki = DecodeCompound(0, encoding); if (spki.tag != 0x30) throw new CoseException("Invalid SPKI"); ArrayList tvl = spki.list; if (tvl.size() != 2) throw new CoseException("Invalid SPKI"); if (tvl.get(0).tag != 0x30) throw new CoseException("Invalid SPKI"); if (tvl.get(0).list.isEmpty() || tvl.get(0).list.size() > 2) { throw new CoseException("Invalid SPKI"); } if (tvl.get(0).list.get(0).tag != 6) throw new CoseException("Invalid SPKI"); // tvl.get(0).list.get(1).tag is an ANY so needs to be checked elsewhere if (tvl.get(1).tag != 3) throw new CoseException("Invalid SPKI"); return tvl; } /** * Decode an array of bytes which is supposed to be an ASN.1 encoded structure. * This code does the decoding w/o any reference to a schema for what is being * decoded so it returns type and value pairs rather than converting the values * to the correct underlying data type. * * One oddity that needs to be observed is that Object Identifiers do not have * the type and length removed from them. This is because we do a byte wise comparison * and started doing the entire item rather than just the value portion. * * M00BUG - we should check that we don't overflow during the decoding process. * * @param offset - starting offset in array to begin decoding * @param encoding - bytes of the ASN.1 encoded value * @return Decoded structure * @throws CoseException - ASN.1 encoding errors */ public static TagValue DecodeCompound(int offset, byte[] encoding) throws CoseException { ArrayList result = new ArrayList(); int retTag = encoding[offset]; // We only decode objects which are compound objects. That means that this bit must be set if ((encoding[offset] & 0x20) != 0x20) throw new CoseException("Invalid structure"); int[] l = DecodeLength(offset+1, encoding); int sequenceLength = l[1]; if (offset + sequenceLength > encoding.length) throw new CoseException("Invalid sequence"); offset += l[0]+1; while (sequenceLength > 0) { int tag = encoding[offset]; l = DecodeLength(offset+1, encoding); if (l[1] > sequenceLength) throw new CoseException("Invalid sequence"); if ((tag & 0x20) != 0) { result.add(DecodeCompound(offset, encoding)); offset += 1 + l[0] + l[1]; sequenceLength -= 1 + l[0] + l[1]; } else { // At some point we might want to fix this. if (tag == 6) { result.add(new TagValue(tag, Arrays.copyOfRange(encoding, offset, offset+l[1]+l[0]+1))); } else { result.add(new TagValue(tag, Arrays.copyOfRange(encoding, offset+l[0]+1, offset+1+l[0]+l[1]))); } offset += 1 + l[0] + l[1]; sequenceLength -= 1 + l[0] + l[1]; } } return new TagValue(retTag, result); } /** * Encode a private key into a PKCS#8 private key structure. * * @param algorithm - EC curve OID * @param keyBytes - raw bytes of the key * @param spki - optional subject public key info structure to include * @return byte array of encoded bytes * @throws CoseException - ASN.1 encoding errors */ public static byte[] EncodePKCS8(byte[] algorithm, byte[] keyBytes, byte[] spki) throws CoseException { // PKCS#8 ::= SEQUENCE { // version INTEGER {0} // privateKeyALgorithm SEQUENCE { // algorithm OID, // parameters ANY // } // privateKey ECPrivateKey, // attributes [0] IMPLICIT Attributes OPTIONAL // publicKey [1] IMPLICIT BIT STRING OPTIONAL // } try { ArrayList xxx = new ArrayList(); xxx.add(new byte[]{2, 1, 0}); xxx.add(algorithm); xxx.add(OctetStringTag); xxx.add(ComputeLength(keyBytes.length)); xxx.add(keyBytes); return Sequence(xxx); } catch (ArrayIndexOutOfBoundsException e) { System.out.print(e.toString()); throw e; } } /** * * Decode a PKCS#8 private key structure, leaving the private key as an octetstring. * @param encodedData bytes containing the private key * @return tag/value from the decoded object * @throws CoseException - ASN.1 encoding errors */ public static ArrayList DecodePKCS8Structure(byte[] encodedData) throws CoseException { TagValue pkcs8 = DecodeCompound(0, encodedData); if (pkcs8.tag != 0x30) throw new CoseException("Invalid PKCS8 structure"); ArrayList retValue = pkcs8.list; if (retValue.size() != 3 && retValue.size() != 4) { throw new CoseException("Invalid PKCS8 structure"); } // Version number - we currently only support one version if (retValue.get(0).tag != IntegerTag && retValue.get(0).value[0] != 0) { throw new CoseException("Invalid PKCS8 structure"); } // Algorithm identifier if (retValue.get(1).tag != 0x30) throw new CoseException("Invalid PKCS8 structure"); if (retValue.get(1).list.isEmpty() || retValue.get(1).list.size() > 2) { throw new CoseException("Invalid PKCS8 structure"); } if (retValue.get(1).list.get(0).tag != 6) throw new CoseException("Invalid PKCS8 structure"); // Dont check the next item as it is an ANY if (retValue.get(2).tag != 4) throw new CoseException("Invalid PKCS8 structure"); // This is attributes, but we are not going to check for correctness. if (retValue.size() == 4 && retValue.get(3).tag != 0xa0) { throw new CoseException("Invalid PKCS8 structure"); } return retValue; } /** * Decode a RSA PKCS#8 private key octet string * * @param pkcs8 The decoded PKCS#8 structure * @return tag/value from the decoded object * @throws CoseException - ASN.1 encoding errors */ public static ArrayList DecodePKCS8RSA(ArrayList pkcs8) throws CoseException { // Decode the contents of the octet string PrivateKey byte[] pk = pkcs8.get(2).value; TagValue pkd = DecodeCompound(0, pk); ArrayList pkdl = pkd.list; if (pkd.tag != 0x30) throw new CoseException("Invalid RSAPrivateKey"); if (pkdl.size() < 8 || pkdl.size() > 11) throw new CoseException("Invalid RSAPrivateKey"); // We don't support multi-prime decoding (version 1), but we do support single prime (version 0) if (pkdl.get(0).tag != IntegerTag && pkcs8.get(0).value[0] != 1) { throw new CoseException("Invalid RSAPrivateKey"); } for (TagValue tagValue : pkd.list) { if(tagValue.tag != IntegerTag) throw new CoseException("Invalid RSAPrivateKey"); } return pkdl; } /** * Decode an EC PKCS#8 private key octet string * * @param pkcs8 The decoded PKCS#8 structure * @return tag/value from the decoded object * @throws CoseException - ASN.1 encoding errors */ public static ArrayList DecodePKCS8EC(ArrayList pkcs8) throws CoseException { // Decode the contents of the octet string PrivateKey byte[] pk = pkcs8.get(2).value; TagValue pkd = DecodeCompound(0, pk); ArrayList pkdl = pkd.list; if (pkd.tag != 0x30) throw new CoseException("Invalid ECPrivateKey"); if (pkdl.size() < 2 || pkdl.size() > 4) throw new CoseException("Invalid ECPrivateKey"); if (pkdl.get(0).tag != 2 && pkcs8.get(0).value[0] != 1) { throw new CoseException("Invalid ECPrivateKey"); } if (pkdl.get(1).tag != 4) throw new CoseException("Invalid ECPrivateKey"); if (pkdl.size() > 2) { if ((pkdl.get(2).tag & 0xff) != 0xA0) { if (pkdl.size() != 3 || (pkdl.get(2).tag & 0xff) != 0xa1) { throw new CoseException("Invalid ECPrivateKey"); } } else { if (pkdl.size() == 4 && (pkdl.get(3).tag & 0xff) != 0xa1) throw new CoseException("Invalid ECPrivateKey"); } } return pkdl; } public static byte[] EncodeSignature(byte[] r, byte[] s) throws CoseException { ArrayList x = new ArrayList(); x.add(UnsignedInteger(r)); x.add(UnsignedInteger(s)); return Sequence(x); } public static byte[] EncodeOctetString(byte[] data) throws CoseException { ArrayList x = new ArrayList(); x.add(OctetStringTag); x.add(ComputeLength(data.length)); x.add(data); return ToBytes(x); } public static byte[] AlgorithmIdentifier(byte[] oid, byte[] params) throws CoseException { ArrayList xxx = new ArrayList(); xxx.add(oid); if (params != null) { xxx.add(params); } return Sequence(xxx); } private static byte[] Sequence(ArrayList members) throws CoseException { byte[] y = ToBytes(members); ArrayList x = new ArrayList(); x.add(SequenceTag); x.add(ComputeLength(y.length)); x.add(y); return ToBytes(x); } private static byte[] UnsignedInteger(byte[] i) throws CoseException { int pad = 0, offset = 0; while (offset < i.length && i[offset] == 0) { offset++; } if (offset == i.length) { return new byte[] {0x02, 0x01, 0x00}; } if ((i[offset] & 0x80) != 0) { pad++; } // M00BUG if the integer is > 127 bytes long with padding int length = i.length - offset; byte[] der = new byte[2 + length + pad]; der[0] = 0x02; der[1] = (byte)(length + pad); System.arraycopy(i, offset, der, 2 + pad, length); return der; } private static byte[] ComputeLength(int x) throws CoseException { if (x <= 127) { return new byte[]{(byte)x}; } else if ( x < 256) { return new byte[]{(byte) 0x81, (byte) x}; } throw new CoseException("Error in ASN1.GetLength"); } private static int[] DecodeLength(int offset, byte[] data) throws CoseException { int length; int i; if ((data[offset] & 0x80) == 0) return new int[]{1, data[offset]}; if (data[offset] == 0x80) { throw new CoseException("Indefinite length encoding not supported"); } length = data[offset] & 0x7f; int retValue = 0; for (i=0; i x) { int l = 0; l = x.stream().map((r) -> r.length).reduce(l, Integer::sum); byte[] b = new byte[l]; l = 0; for (byte[] r : x) { System.arraycopy(r, 0, b, l, r.length); l += r.length; } return b; } }