package peergos.shared.user; import peergos.shared.cbor.*; import peergos.shared.corenode.*; import peergos.shared.crypto.asymmetric.*; import java.util.*; import java.util.stream.*; public class TofuKeyStore implements Cborable { private final Map<String, List<UserPublicKeyLink>> chains; private final Map<PublicSigningKey, String> reverseLookup = new HashMap<>(); public TofuKeyStore(Map<String, List<UserPublicKeyLink>> chains) { this.chains = chains; updateReverseLookup(); } public TofuKeyStore() { this(new HashMap<>()); } public Optional<PublicSigningKey> getPublicKey(String username) { List<UserPublicKeyLink> chain = chains.get(username); if (chain == null) return Optional.empty(); return Optional.of(chain.get(chain.size() - 1).owner); } public Optional<String> getUsername(PublicSigningKey signer) { String name = reverseLookup.get(signer); return Optional.ofNullable(name); } public List<UserPublicKeyLink> getChain(String username) { return chains.getOrDefault(username, Collections.emptyList()); } public void updateChain(String username, List<UserPublicKeyLink> tail) { UserPublicKeyLink.validChain(tail, username); List<UserPublicKeyLink> existing = getChain(username); List<UserPublicKeyLink> merged = UserPublicKeyLink.merge(existing, tail); chains.put(username, merged); PublicSigningKey owner = tail.get(tail.size() - 1).owner; reverseLookup.put(owner, username); } private void updateReverseLookup() { reverseLookup.clear(); reverseLookup.putAll(chains.entrySet().stream() .collect(Collectors.toMap( e -> e.getValue().get(e.getValue().size() - 1).owner, e -> e.getKey()))); } @Override public CborObject toCbor() { SortedMap<CborObject, CborObject> state = new TreeMap<>(); chains.forEach((name, chain) -> state.put(new CborObject.CborString(name), new CborObject.CborList(chain.stream() .map(link -> link.toCbor()) .collect(Collectors.toList())))); return new CborObject.CborMap(state); } public static TofuKeyStore fromCbor(CborObject cbor) { if (! (cbor instanceof CborObject.CborMap)) throw new IllegalStateException("Invalid cbor for Tofu key store: " + cbor); Map<String, List<UserPublicKeyLink>> chains = new HashMap<>(); SortedMap<CborObject, CborObject> values = ((CborObject.CborMap) cbor).values; for (CborObject key: values.keySet()) { if (key instanceof CborObject.CborString) { String name = ((CborObject.CborString) key).value; CborObject value = values.get(key); if (value instanceof CborObject.CborList) { List<UserPublicKeyLink> chain = ((CborObject.CborList) value).value.stream() .map(UserPublicKeyLink::fromCbor) .collect(Collectors.toList()); UserPublicKeyLink.validChain(chain, name); chains.put(name, chain); } else throw new IllegalStateException("Invalid value in Tofu key store map: " + value); } else throw new IllegalStateException("Invalid key in Tofu key store map: " + key); } return new TofuKeyStore(chains); } }