@Override public String toString() { AbstractKey k = this instanceof PrivateKey ? getPublicKey() : this; return info().toString() + ":" + info().getBase64Tag(); }
public final KeyAddress getShortAddress() { if( shortAddress == null ) shortAddress = address(false, 0); return shortAddress; }
/** * Calculate keyId for a given key (should be either {@link PublicKey} or {@link PrivateKey}). the keyId is the same * for public and private key and can be used to store/access keys in Map ({@link Bytes} instances can be used as * Map keys. * <p> * Use {@link #extractKeyId(byte[])} to get a keyId from a packed extended signature, find the proper key, than * {@link #verify(PublicKey, byte[], byte[])} the data. It uses corresponding {@link PublicKey#fingerprint()}. * * @param key key to calculate Id * * @return calculated key id */ static public Bytes keyId(AbstractKey key) { if (key instanceof PrivateKey) return new Bytes(key.getPublicKey().fingerprint()); return new Bytes(key.fingerprint()); }
@Override public final boolean isMatchingKeyAddress(KeyAddress other) { return other.isLong() ? getLongAddress().isMatchingKeyAddress(other) : getShortAddress().isMatchingKeyAddress(other); }
@Test public void matchTypeAndTag() throws Exception { // 2 different private keys AbstractKey k1 = TestKeys.privateKey(0); AbstractKey k2 = TestKeys.privateKey(1); assertTrue(k1.info().matchType(k2.info())); assertFalse(k1.info().matchTag(k2.info())); // public matches private, not vice versa AbstractKey k3 = k1.getPublicKey(); assertTrue(k1.info().matchType(k3.info())); assertFalse(k3.info().matchType(k1.info())); assertTrue(k1.info().matchTag(k3.info())); // public keys do not match each other! assertFalse(k3.info().matchType(k3.info())); assertFalse(k3.info().matchType(k2.getPublicKey().info())); // Check AES match algorythm and tag AbstractKey k4 = new SymmetricKey(); assertFalse(k2.matchType(k4)); assertFalse(k3.matchType(k4)); assertFalse(k4.matchType(k2)); assertFalse(k4.matchType(k3)); assertFalse(k4.matchTag(k2)); assertFalse(k4.matchTag(k3)); AbstractKey k5 = new SymmetricKey(); assertTrue(k4.matchType(k5)); assertTrue(k5.matchType(k4)); assertFalse(k4.matchTag(k5)); k4.setTag("Hello"); k5.setTag("Hello"); }
/** * Check role is allowed to keys * * @param keys is set of keys * @return true if role is allowed to keys */ @Override public boolean isAllowedForKeys(Set<? extends AbstractKey> keys) { if(!super.isAllowedForKeys(keys)) { return false; } boolean allMatch1 = anonymousIds.stream().allMatch(anonId -> keys.stream().anyMatch(key -> { try { return key.matchAnonymousId(anonId.getBytes()); } catch (IOException e) { return false; } })); boolean allMatch2 = keyRecords.values().stream().allMatch( kr -> keys.stream().anyMatch(k -> k.getPublicKey().equals(kr.getPublicKey()))); boolean allMatch3 = keyAddresses.stream().allMatch(address -> keys.stream().anyMatch(key -> key.isMatchingKeyAddress(address))); return allMatch1 && allMatch2 && allMatch3; }
/** * Create new revision to be changed, signed sealed and then ready to approve. Created "revision" contract is a copy * of this contract, with all fields and references correctly set. After this call one need to change mutable * fields, add signing keys, seal it and then apss to Universa network for approval. * * @param keys initially added and signer keys. Role "creator" is set to addresses of these keys * @param transactional is {@link Transactional} section to create new revision with * @return new revision of this contract, identical to this one, to be modified. */ public synchronized Contract createRevisionWithAddress(Collection<?> keys, Transactional transactional) { Contract newRevision = createRevision(transactional); Set<KeyAddress> aids = new HashSet<>(); AtomicBoolean returnNull = new AtomicBoolean(false); keys.forEach(k -> { if (k instanceof AbstractKey) aids.add(((AbstractKey) k).getPublicKey().getShortAddress()); else if (k instanceof KeyAddress) aids.add((KeyAddress)k); else returnNull.set(true); }); newRevision.setCreatorKeys(aids); if (returnNull.get()) return null; return newRevision; }
private void testMatch(boolean use384) throws KeyAddress.IllegalAddressException { KeyAddress a = key1.address(use384, 7); KeyAddress b = new KeyAddress(a.toString()); assertEquals(7, b.getTypeMark()); assertEquals(7, a.getTypeMark()); assertTrue(b.isMatchingKey(key1)); assertTrue(a.isMatchingKeyAddress(b)); assertTrue(b.isMatchingKeyAddress(a)); assertTrue(key1.isMatchingKey(key1)); assertTrue(key1.isMatchingKeyAddress(a)); assertTrue(key1.isMatchingKeyAddress(b)); byte[] pack = a.getPacked(); pack[7] ^= 71; try { new KeyAddress(pack); fail("must throw error on spoiled address"); } catch(KeyAddress.IllegalAddressException e) { } }
@NonNull private HashMap<String, AbstractKey> prepareSigners(Binder payload) { HashMap<String, AbstractKey> sigIds = new HashMap<>(); if (signers != null && !signers.isEmpty()) { ArrayList<Binder> s = payload.set("signers", new ArrayList<>()); int i = 0; for (Binder b : signers.values()) { final String id = b.getStringOrThrow("id"); final AbstractKey key = (AbstractKey) b.get("key"); sigIds.put(id, key); Binder signerData = new Binder( "id", id, "key", key.getPublicKey().pack(), "data", b.getBinder("data") ); s.add(signerData); } } return sigIds; }
@Test public void unpackKey() throws Exception { AbstractKey k1 = TestKeys.privateKey(3).getPublicKey(); AbstractKey kx = AbstractKey.fromBinder(k1.toBinder()); assertEquals(k1, kx); k1 = SymmetricKey.fromPassword("helluva", 4096); kx = AbstractKey.fromBinder(k1.toBinder()); assertEquals(k1, kx); k1 = TestKeys.privateKey(2); kx = AbstractKey.fromBinder(k1.toBinder()); assertEquals(k1, kx); }
@Test public void saveAndRestore() throws Exception { KeyRing kr = new KeyRing(); SymmetricKey sk1 = new SymmetricKey(); SymmetricKey sk2 = new SymmetricKey(); AbstractKey privateKey = TestKeys.privateKey(0); AbstractKey publicKey1 = TestKeys.privateKey(1).getPublicKey(); AbstractKey publicKey2 = privateKey.getPublicKey(); kr.addKeys( sk1, sk2, privateKey, publicKey1, publicKey2); Binder b = kr.toBinder(); KeyRing kr2 = KeyRing.fromBinder(b); for(AbstractKey k: kr2.keySet()) { assertTrue(kr2.contains(k)); assertTrue(kr.contains(k)); } assertEquals(kr, kr2); }
/** * Check that the packed anonymousId matches current key. Use {@link #createAnonymousId()} to get a random anonymous * id for this key. * * @param packedId * @return true if it matches. * @throws IOException */ public boolean matchAnonymousId(@NonNull byte[] packedId) throws IOException { assert (packedId.length == 64); HMAC hmac = new HMAC(fingerprint()); hmac.update(packedId, 0, 32); byte[] idDigest = Arrays.copyOfRange(packedId, 32, 64); return Arrays.equals(hmac.digest(), idDigest); }
public void setTag(String tag) { try { setTag(tag.getBytes("utf-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException("utf8 is not supported?"); } }
@Override public boolean isMatchingKey(AbstractKey key) { return getShortAddress().isMatchingKeyAddress(key.getShortAddress()); }
@Override public Binder toBinder() { ArrayList<Binder> result = new ArrayList<>(); for(AbstractKey k: keys) result.add(k.toBinder()); return new Binder("keys", result); }
/** * Create new revision to be changed, signed sealed and then ready to approve. Created "revision" contract is a copy * of this contract, with all fields and references correctly set. After this call one need to change mutable * fields, add signing keys, seal it and then apss to Universa network for approval. * * @param keys initially added and signer keys. Role "creator" is set to anonymous ids of these keys * @param transactional is {@link Transactional} section to create new revision with * @return new revision of this contract, identical to this one, to be modified. */ public synchronized Contract createRevisionAnonymously(Collection<?> keys, Transactional transactional) { Contract newRevision = createRevision(transactional); Set<AnonymousId> aids = new HashSet<>(); AtomicBoolean returnNull = new AtomicBoolean(false); keys.forEach(k -> { if (k instanceof AbstractKey) aids.add(AnonymousId.fromBytes(((AbstractKey) k).createAnonymousId())); else if (k instanceof AnonymousId) aids.add((AnonymousId)k); else returnNull.set(true); }); newRevision.setCreatorKeys(aids); if (returnNull.get()) return null; return newRevision; }
/** * Create a random (e.g. every call a different) sequence of bytes that identidy this key. There can almost infinite * number if anonynous ids for e key (more than 1.0E77), so it is really anonymous way to identify some key. The * anonymousId for public and private keys are the same. * <p> * Anonymous ID size is 64 bytes: first are 32 random bytes, last are HMAC(key, sha256) of these random bytes. * <p> * The most important thing about anonymous ids is that every time this call generates new id for the same key, * providing anonymous but exact identification of a key. * <p> * To check that the key matches some anonymousId, use {@link #matchAnonymousId(byte[])}. * <p> * Therefore, the private key fingerprint is its public key fingerprint. The public key fingerprint is calculated * using some hash over it's parameters, see {@link PublicKey#fingerprint()} * * @return */ public byte[] createAnonymousId() { byte[] rvector = Do.randomBytes(32); HMAC hmac = new HMAC(fingerprint()); hmac.update(rvector); byte[] result = new byte[64]; System.arraycopy(rvector, 0, result, 0, 32); System.arraycopy(hmac.digest(), 0, result, 32, 32); return result; }
@Test public void privateKeyMustHaveInfo() throws Exception { AbstractKey prk = TestKeys.privateKey(3); AbstractKey puk = prk.getPublicKey(); KeyInfo h = prk.info(); assertEquals(KeyInfo.Algorythm.RSAPrivate, h.getAlgorythm()); assertEquals(KeyInfo.PRF.None, h.getPRF()); assertEquals(5, h.getTag().length); assertArrayEquals(puk.info().getTag(), h.getTag()); // Bytes.dump(h.pack()); }