diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java index 3ee8d70af..5750c0e3b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java @@ -12,6 +12,27 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; @NotNullByDefault public interface ContactExchangeTask { + /** + * Label for deriving Alice's header key from the master secret. + */ + String ALICE_KEY_LABEL = + "org.briarproject.bramble.contact/ALICE_HEADER_KEY"; + + /** + * Label for deriving Bob's header key from the master secret. + */ + String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY"; + + /** + * Label for deriving Alice's key binding nonce from the master secret. + */ + String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE"; + + /** + * Label for deriving Bob's key binding nonce from the master secret. + */ + String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE"; + /** * Exchanges contact information with a remote peer. */ diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java index 5da0e4218..fd2c0ffc9 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java @@ -27,95 +27,74 @@ public interface CryptoComponent { KeyParser getMessageKeyParser(); /** - * Derives a stream header key from the given master secret. - * @param alice whether the key is for use by Alice or Bob. + * Derives another secret key from the given secret key. + * + * @param label a namespaced label indicating the purpose of the derived + * key, to prevent it from being repurposed or colliding with a key derived + * for another purpose */ - SecretKey deriveHeaderKey(SecretKey master, boolean alice); + SecretKey deriveKey(String label, SecretKey k, byte[]... inputs); /** - * Derives a message authentication code key from the given master secret. - * @param alice whether the key is for use by Alice or Bob. + * Derives a nonce from the given secret key that can be used for key + * binding. + * + * @param label a namespaced label indicating the purpose of this nonce, + * to prevent it from being repurposed or colliding with a nonce derived + * for another purpose */ - SecretKey deriveMacKey(SecretKey master, boolean alice); - - /** - * Derives a nonce from the given master secret for one of the parties to - * sign. - * @param alice whether the nonce is for use by Alice or Bob. - */ - byte[] deriveSignatureNonce(SecretKey master, boolean alice); + byte[] deriveKeyBindingNonce(String label, SecretKey k); /** * Derives a commitment to the provided public key. *
- * Part of BQP. + * Used by the key exchange protocol. * * @param publicKey the public key * @return the commitment to the provided public key. */ - byte[] deriveKeyCommitment(byte[] publicKey); + byte[] deriveKeyCommitment(PublicKey publicKey); /** * Derives a common shared secret from two public keys and one of the * corresponding private keys. - * - * Part of BQP. * - * @param theirPublicKey the ephemeral public key of the remote party - * @param ourKeyPair our ephemeral keypair - * @param alice true if ourKeyPair belongs to Alice + * @param label a namespaced label indicating the purpose of this shared + * secret, to prevent it from being repurposed or colliding with a shared + * secret derived for another purpose + * @param theirPublicKey the public key of the remote party + * @param ourKeyPair the key pair of the local party + * @param alice true if the local party is Alice * @return the shared secret */ - SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair, - boolean alice) throws GeneralSecurityException; + SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey, + KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException; /** * Derives the content of a confirmation record. * - * Part of BQP. + * Used by the key exchange protocol. * * @param sharedSecret the common shared secret - * @param theirPayload the commit payload from the remote party - * @param ourPayload the commit payload we sent + * @param theirPayload the key exchange payload of the remote party + * @param ourPayload the key exchange payload of the local party * @param theirPublicKey the ephemeral public key of the remote party - * @param ourKeyPair our ephemeral keypair - * @param alice true if ourKeyPair belongs to Alice + * @param ourKeyPair our ephemeral key pair of the local party + * @param alice true if the local party is Alice * @param aliceRecord true if the confirmation record is for use by Alice * @return the confirmation record */ byte[] deriveConfirmationRecord(SecretKey sharedSecret, byte[] theirPayload, byte[] ourPayload, - byte[] theirPublicKey, KeyPair ourKeyPair, + PublicKey theirPublicKey, KeyPair ourKeyPair, boolean alice, boolean aliceRecord); - /** - * Derives a master secret from the given shared secret. - * - * Part of BQP. - * - * @param sharedSecret the common shared secret - * @return the master secret - */ - SecretKey deriveMasterSecret(SecretKey sharedSecret); - - /** - * Derives a master secret from two public keys and one of the corresponding - * private keys. - * - * This is a helper method that calls - * deriveMasterSecret(deriveSharedSecret(theirPublicKey, ourKeyPair, alice)) - * - * @param theirPublicKey the ephemeral public key of the remote party - * @param ourKeyPair our ephemeral keypair - * @param alice true if ourKeyPair belongs to Alice - * @return the shared secret - */ - SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair, - boolean alice) throws GeneralSecurityException; - /** * Derives initial transport keys for the given transport in the given * rotation period from the given master secret. + * + * Used by the transport security protocol. + * * @param alice whether the keys are for use by Alice or Bob. */ TransportKeys deriveTransportKeys(TransportId t, SecretKey master, @@ -124,18 +103,25 @@ public interface CryptoComponent { /** * Rotates the given transport keys to the given rotation period. If the * keys are for a future rotation period they are not rotated. + * + * Used by the transport security protocol. */ TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod); - /** Encodes the pseudo-random tag that is used to recognise a stream. */ + /** + * Encodes the pseudo-random tag that is used to recognise a stream. + * + * Used by the transport security protocol. + */ void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion, long streamNumber); /** * Signs the given byte[] with the given ECDSA private key. * - * @param label A label specific to this signature - * to ensure that the signature cannot be repurposed + * @param label a namespaced label indicating the purpose of this + * signature, to prevent it from being repurposed or colliding with a + * signature created for another purpose */ byte[] sign(String label, byte[] toSign, byte[] privateKey) throws GeneralSecurityException; @@ -153,8 +139,9 @@ public interface CryptoComponent { * Verifies that the given signature is valid for the signed data * and the given ECDSA public key. * - * @param label A label that was specific to this signature - * to ensure that the signature cannot be repurposed + * @param label a namespaced label indicating the purpose of this + * signature, to prevent it from being repurposed or colliding with a + * signature created for another purpose * @return true if the signature was valid, false otherwise. */ boolean verify(String label, byte[] signedData, byte[] publicKey, @@ -175,23 +162,22 @@ public interface CryptoComponent { * Returns the hash of the given inputs. The inputs are unambiguously * combined by prefixing each input with its length. * - * @param label A label specific to this hash to ensure that hashes - * calculated for distinct purposes don't collide. + * @param label a namespaced label indicating the purpose of this hash, to + * prevent it from being repurposed or colliding with a hash created for + * another purpose */ byte[] hash(String label, byte[]... inputs); - /** - * Returns the length of hashes produced by - * the {@link CryptoComponent#hash(String, byte[]...)} method. - */ - int getHashLength(); - /** * Returns a message authentication code with the given key over the * given inputs. The inputs are unambiguously combined by prefixing each * input with its length. + * + * @param label a namespaced label indicating the purpose of this MAC, to + * prevent it from being repurposed or colliding with a MAC created for + * another purpose */ - byte[] mac(SecretKey macKey, byte[]... inputs); + byte[] mac(String label, SecretKey macKey, byte[]... inputs); /** * Encrypts and authenticates the given plaintext so it can be written to diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java index 0963e0049..b9793b477 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorId.java @@ -16,7 +16,7 @@ public class AuthorId extends UniqueId { /** * Label for hashing authors to calculate their identities. */ - public static final String LABEL = "org.briarproject.bramble.AUTHOR_ID"; + public static final String LABEL = "org.briarproject.bramble/AUTHOR_ID"; public AuthorId(byte[] id) { super(id); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java index 76204bdfb..807055d6e 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/keyagreement/KeyAgreementConstants.java @@ -5,7 +5,7 @@ public interface KeyAgreementConstants { /** * The current version of the BQP protocol. */ - byte PROTOCOL_VERSION = 2; + byte PROTOCOL_VERSION = 3; /** * The length of the record header in bytes. @@ -22,7 +22,10 @@ public interface KeyAgreementConstants { */ int COMMIT_LENGTH = 16; - long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds + /** + * The connection timeout in milliseconds. + */ + long CONNECTION_TIMEOUT = 20 * 1000; /** * The transport identifier for Bluetooth. @@ -33,4 +36,16 @@ public interface KeyAgreementConstants { * The transport identifier for LAN. */ int TRANSPORT_ID_LAN = 1; + + /** + * Label for deriving the shared secret. + */ + String SHARED_SECRET_LABEL = + "org.briarproject.bramble.keyagreement/SHARED_SECRET"; + + /** + * Label for deriving the master secret. + */ + String MASTER_SECRET_LABEL = + "org.briarproject.bramble.keyagreement/MASTER_SECRET"; } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java index cdd6b2d3f..d118ed9c0 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/GroupId.java @@ -15,7 +15,7 @@ public class GroupId extends UniqueId { /** * Label for hashing groups to calculate their identifiers. */ - public static final String LABEL = "org.briarproject.bramble.GROUP_ID"; + public static final String LABEL = "org.briarproject.bramble/GROUP_ID"; public GroupId(byte[] id) { super(id); diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java index 84389bbcc..07487eb8c 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java @@ -16,7 +16,7 @@ public class MessageId extends UniqueId { /** * Label for hashing messages to calculate their identifiers. */ - public static final String LABEL = "org.briarproject.bramble.MESSAGE_ID"; + public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID"; public MessageId(byte[] id) { super(id); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java index c49360e5b..5a86874ec 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java @@ -141,8 +141,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { } // Derive the header keys for the transport streams - SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true); - SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false); + SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, + masterSecret); + SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret); // Create the readers InputStream streamReader = @@ -156,8 +157,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { BdfWriter w = bdfWriterFactory.createWriter(streamWriter); // Derive the nonces to be signed - byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true); - byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false); + byte[] aliceNonce = crypto.deriveKeyBindingNonce(ALICE_NONCE_LABEL, + masterSecret); + byte[] bobNonce = crypto.deriveKeyBindingNonce(BOB_NONCE_LABEL, + masterSecret); // Exchange pseudonyms, signed nonces, and timestamps long localTimestamp = clock.currentTimeMillis(); @@ -312,8 +315,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { return contactId; } - private void tryToClose(DuplexTransportConnection conn, - boolean exception) { + private void tryToClose(DuplexTransportConnection conn, boolean exception) { try { LOG.info("Closing connection"); conn.getReader().dispose(exception, true); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index e92babdc0..0b1a7f235 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -30,7 +30,6 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.KeyParameter; -import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.Provider; @@ -65,39 +64,25 @@ class CryptoComponentImpl implements CryptoComponent { private static final int PBKDF_SALT_BYTES = 32; // 256 bits private static final int PBKDF_TARGET_MILLIS = 500; private static final int PBKDF_SAMPLES = 30; - private static final int HASH_SIZE = 256 / 8; - private static byte[] ascii(String s) { - return s.getBytes(Charset.forName("US-ASCII")); - } - - // KDF labels for contact exchange stream header key derivation - private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY"); - private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY"); - // KDF labels for contact exchange signature nonce derivation - private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE"); - private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE"); // Hash label for BQP public key commitment derivation - private static final String COMMIT = - "org.briarproject.bramble.COMMIT"; - // Hash label for shared secret derivation - private static final String SHARED_SECRET = - "org.briarproject.bramble.SHARED_SECRET"; + private static final String COMMIT_LABEL = + "org.briarproject.bramble.keyagreement/COMMIT"; // KDF label for BQP confirmation key derivation - private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY"); - // KDF label for master key derivation - private static final byte[] MASTER_KEY = ascii("MASTER_KEY"); + private static final String CONFIRMATION_KEY_LABEL = + "org.briarproject.bramble.keyagreement/CONFIRMATION_KEY"; + // MAC label for BQP confirmation record + private static final String CONFIRMATION_MAC_LABEL = + "org.briarproject.bramble.keyagreement/CONFIRMATION_MAC"; + // KDF labels for tag key derivation - private static final byte[] A_TAG = ascii("ALICE_TAG_KEY"); - private static final byte[] B_TAG = ascii("BOB_TAG_KEY"); + private static final String A_TAG = "ALICE_TAG_KEY"; + private static final String B_TAG = "BOB_TAG_KEY"; // KDF labels for header key derivation - private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY"); - private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY"); - // KDF labels for MAC key derivation - private static final byte[] A_MAC = ascii("ALICE_MAC_KEY"); - private static final byte[] B_MAC = ascii("BOB_MAC_KEY"); + private static final String A_HEADER = "ALICE_HEADER_KEY"; + private static final String B_HEADER = "BOB_HEADER_KEY"; // KDF label for key rotation - private static final byte[] ROTATE = ascii("ROTATE"); + private static final String ROTATE = "ROTATE"; private final SecureRandom secureRandom; private final ECKeyPairGenerator agreementKeyPairGenerator; @@ -263,25 +248,19 @@ class CryptoComponentImpl implements CryptoComponent { } @Override - public SecretKey deriveHeaderKey(SecretKey master, - boolean alice) { - return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE)); + public SecretKey deriveKey(String label, SecretKey k, + byte[]... inputs) { + return new SecretKey(macKdf(label, k, inputs)); } @Override - public SecretKey deriveMacKey(SecretKey master, boolean alice) { - return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC)); + public byte[] deriveKeyBindingNonce(String label, SecretKey k) { + return macKdf(label, k); } @Override - public byte[] deriveSignatureNonce(SecretKey master, - boolean alice) { - return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE); - } - - @Override - public byte[] deriveKeyCommitment(byte[] publicKey) { - byte[] hash = hash(COMMIT, publicKey); + public byte[] deriveKeyCommitment(PublicKey publicKey) { + byte[] hash = hash(COMMIT_LABEL, publicKey.getEncoded()); // The output is the first COMMIT_LENGTH bytes of the hash byte[] commitment = new byte[COMMIT_LENGTH]; System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH); @@ -289,55 +268,45 @@ class CryptoComponentImpl implements CryptoComponent { } @Override - public SecretKey deriveSharedSecret(byte[] theirPublicKey, + public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey, KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException { PrivateKey ourPriv = ourKeyPair.getPrivate(); - PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey); - byte[] raw = performRawKeyAgreement(ourPriv, theirPub); + byte[] raw = performRawKeyAgreement(ourPriv, theirPublicKey); byte[] alicePub, bobPub; if (alice) { alicePub = ourKeyPair.getPublic().getEncoded(); - bobPub = theirPublicKey; + bobPub = theirPublicKey.getEncoded(); } else { - alicePub = theirPublicKey; + alicePub = theirPublicKey.getEncoded(); bobPub = ourKeyPair.getPublic().getEncoded(); } - return new SecretKey(hash(SHARED_SECRET, raw, alicePub, bobPub)); + return new SecretKey(hash(label, raw, alicePub, bobPub)); } @Override public byte[] deriveConfirmationRecord(SecretKey sharedSecret, - byte[] theirPayload, byte[] ourPayload, byte[] theirPublicKey, + byte[] theirPayload, byte[] ourPayload, PublicKey theirPublicKey, KeyPair ourKeyPair, boolean alice, boolean aliceRecord) { - SecretKey ck = new SecretKey(macKdf(sharedSecret, CONFIRMATION_KEY)); + SecretKey ck = deriveKey(CONFIRMATION_KEY_LABEL, sharedSecret); byte[] alicePayload, alicePub, bobPayload, bobPub; if (alice) { alicePayload = ourPayload; alicePub = ourKeyPair.getPublic().getEncoded(); bobPayload = theirPayload; - bobPub = theirPublicKey; + bobPub = theirPublicKey.getEncoded(); } else { alicePayload = theirPayload; - alicePub = theirPublicKey; + alicePub = theirPublicKey.getEncoded(); bobPayload = ourPayload; bobPub = ourKeyPair.getPublic().getEncoded(); } - if (aliceRecord) - return macKdf(ck, alicePayload, alicePub, bobPayload, bobPub); - else - return macKdf(ck, bobPayload, bobPub, alicePayload, alicePub); - } - - @Override - public SecretKey deriveMasterSecret(SecretKey sharedSecret) { - return new SecretKey(macKdf(sharedSecret, MASTER_KEY)); - } - - @Override - public SecretKey deriveMasterSecret(byte[] theirPublicKey, - KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException { - return deriveMasterSecret(deriveSharedSecret( - theirPublicKey, ourKeyPair, alice)); + if (aliceRecord) { + return macKdf(CONFIRMATION_MAC_LABEL, ck, alicePayload, alicePub, + bobPayload, bobPub); + } else { + return macKdf(CONFIRMATION_MAC_LABEL, ck, bobPayload, bobPub, + alicePayload, alicePub); + } } @Override @@ -396,19 +365,19 @@ class CryptoComponentImpl implements CryptoComponent { private SecretKey rotateKey(SecretKey k, long rotationPeriod) { byte[] period = new byte[INT_64_BYTES]; ByteUtils.writeUint64(rotationPeriod, period, 0); - return new SecretKey(macKdf(k, ROTATE, period)); + return deriveKey(ROTATE, k, period); } private SecretKey deriveTagKey(SecretKey master, TransportId t, boolean alice) { byte[] id = StringUtils.toUtf8(t.getString()); - return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id)); + return deriveKey(alice ? A_TAG : B_TAG, master, id); } private SecretKey deriveHeaderKey(SecretKey master, TransportId t, boolean alice) { byte[] id = StringUtils.toUtf8(t.getString()); - return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id)); + return deriveKey(alice ? A_HEADER : B_HEADER, master, id); } @Override @@ -513,14 +482,13 @@ class CryptoComponentImpl implements CryptoComponent { } @Override - public int getHashLength() { - return HASH_SIZE; - } - - @Override - public byte[] mac(SecretKey macKey, byte[]... inputs) { + public byte[] mac(String label, SecretKey macKey, byte[]... inputs) { + byte[] labelBytes = StringUtils.toUtf8(label); Digest mac = new Blake2sDigest(macKey.getBytes()); byte[] length = new byte[INT_32_BYTES]; + ByteUtils.writeUint32(labelBytes.length, length, 0); + mac.update(length, 0, length.length); + mac.update(labelBytes, 0, labelBytes.length); for (byte[] input : inputs) { ByteUtils.writeUint32(input.length, length, 0); mac.update(length, 0, length.length); @@ -614,26 +582,11 @@ class CryptoComponentImpl implements CryptoComponent { // Key derivation function based on a pseudo-random function - see // NIST SP 800-108, section 5.1 - private byte[] macKdf(SecretKey key, byte[]... inputs) { - // Initialise the PRF - Digest prf = new Blake2sDigest(key.getBytes()); - // The output of the PRF must be long enough to use as a key - int macLength = prf.getDigestSize(); - if (macLength < SecretKey.LENGTH) throw new IllegalStateException(); - // Calculate the PRF over the concatenated length-prefixed inputs - byte[] length = new byte[INT_32_BYTES]; - for (byte[] input : inputs) { - ByteUtils.writeUint32(input.length, length, 0); - prf.update(length, 0, length.length); - prf.update(input, 0, input.length); - } - byte[] mac = new byte[macLength]; - prf.doFinal(mac, 0); - // The output is the first SecretKey.LENGTH bytes of the MAC - if (mac.length == SecretKey.LENGTH) return mac; - byte[] truncated = new byte[SecretKey.LENGTH]; - System.arraycopy(mac, 0, truncated, 0, truncated.length); - return truncated; + private byte[] macKdf(String label, SecretKey k, byte[]... inputs) { + byte[] mac = mac(label, k, inputs); + // The output of the PRF must be usable as a key + if (mac.length != SecretKey.LENGTH) throw new IllegalStateException(); + return mac; } // Password-based key derivation function - see PKCS#5 v2.1, section 5.2 diff --git a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java index 992cb006b..2ed0b9521 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/keyagreement/KeyAgreementConnector.java @@ -70,8 +70,8 @@ class KeyAgreementConnector { public Payload listen(KeyPair localKeyPair) { LOG.info("Starting BQP listeners"); // Derive commitment - byte[] commitment = crypto.deriveKeyCommitment( - localKeyPair.getPublic().getEncoded()); + byte[] commitment = + crypto.deriveKeyCommitment(localKeyPair.getPublic()); // Start all listeners and collect their descriptors List- * Derives two nonces and two mac keys from the secret master key. + * Derives two nonces and two MAC keys from the shared secret key. * The other introducee's nonce and MAC key are added to the localState. *
* Our nonce is signed with the local author's long-term private key. @@ -448,21 +451,23 @@ class IntroduceeManager { private void deriveMacKeysAndNonces(BdfDictionary localState, LocalAuthor author, SecretKey secretKey, boolean alice) throws FormatException, GeneralSecurityException { - // Derive two nonces and a MAC key from the secret master key - byte[] ourNonce = - cryptoComponent.deriveSignatureNonce(secretKey, alice); - byte[] theirNonce = - cryptoComponent.deriveSignatureNonce(secretKey, !alice); - SecretKey macKey = cryptoComponent.deriveMacKey(secretKey, alice); - SecretKey theirMacKey = cryptoComponent.deriveMacKey(secretKey, !alice); + // Derive two nonces and MAC keys from the shared secret key + byte[] ourNonce = cryptoComponent.deriveKeyBindingNonce( + alice ? ALICE_NONCE_LABEL : BOB_NONCE_LABEL, secretKey); + byte[] theirNonce = cryptoComponent.deriveKeyBindingNonce( + alice ? BOB_NONCE_LABEL : ALICE_NONCE_LABEL, secretKey); + SecretKey ourMacKey = cryptoComponent.deriveKey( + alice ? ALICE_MAC_KEY_LABEL : BOB_MAC_KEY_LABEL, secretKey); + SecretKey theirMacKey = cryptoComponent.deriveKey( + alice ? BOB_MAC_KEY_LABEL : ALICE_MAC_KEY_LABEL, secretKey); // Save the other nonce and MAC key for the verification localState.put(NONCE, theirNonce); localState.put(MAC_KEY, theirMacKey.getBytes()); // Sign our nonce with our long-term identity public key - byte[] sig = cryptoComponent - .sign(SIGNING_LABEL_RESPONSE, ourNonce, author.getPrivateKey()); + byte[] sig = cryptoComponent.sign(SIGNING_LABEL, ourNonce, + author.getPrivateKey()); // Calculate a MAC over identity public key, ephemeral public key, // transport properties and timestamp. @@ -472,7 +477,7 @@ class IntroduceeManager { BdfList toMacList = BdfList.of(author.getPublicKey(), publicKeyBytes, tp, ourTime); byte[] toMac = clientHelper.toByteArray(toMacList); - byte[] mac = cryptoComponent.mac(macKey, toMac); + byte[] mac = cryptoComponent.mac(MAC_LABEL, ourMacKey, toMac); // Add MAC and signature to localState, so it can be included in ACK localState.put(OUR_MAC, mac); @@ -486,7 +491,7 @@ class IntroduceeManager { byte[] key = localState.getRaw(PUBLIC_KEY); // Verify the signature - if (!cryptoComponent.verify(SIGNING_LABEL_RESPONSE, nonce, key, sig)) { + if (!cryptoComponent.verify(SIGNING_LABEL, nonce, key, sig)) { LOG.warning("Invalid nonce signature in ACK"); throw new GeneralSecurityException(); } @@ -506,7 +511,7 @@ class IntroduceeManager { long timestamp = localState.getLong(TIME); BdfList toMacList = BdfList.of(pubKey, ePubKey, tp, timestamp); byte[] toMac = clientHelper.toByteArray(toMacList); - byte[] calculatedMac = cryptoComponent.mac(macKey, toMac); + byte[] calculatedMac = cryptoComponent.mac(MAC_LABEL, macKey, toMac); if (!Arrays.equals(mac, calculatedMac)) { LOG.warning("Received ACK with invalid MAC"); throw new GeneralSecurityException(); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java index 0453530c9..2144b4979 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java @@ -53,6 +53,7 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.INTR import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LENGTH; import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME; @@ -66,6 +67,7 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE; import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; +import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL; import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE; import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; @@ -74,7 +76,6 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK; import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.briarproject.briar.introduction.IntroduceeManager.SIGNING_LABEL_RESPONSE; import static org.hamcrest.Matchers.array; import static org.hamcrest.Matchers.samePropertyValuesAs; import static org.junit.Assert.assertFalse; @@ -266,7 +267,7 @@ public class IntroduceeManagerTest extends BriarTestCase { ); context.checking(new Expectations() {{ - oneOf(cryptoComponent).verify(SIGNING_LABEL_RESPONSE, nonce, + oneOf(cryptoComponent).verify(SIGNING_LABEL, nonce, introducee2.getAuthor().getPublicKey(), sig); will(returnValue(false)); }}); @@ -296,7 +297,7 @@ public class IntroduceeManagerTest extends BriarTestCase { state.put(SIGNATURE, sig); context.checking(new Expectations() {{ - oneOf(cryptoComponent).verify(SIGNING_LABEL_RESPONSE, nonce, + oneOf(cryptoComponent).verify(SIGNING_LABEL, nonce, publicKeyBytes, sig); will(returnValue(true)); }}); @@ -330,7 +331,8 @@ public class IntroduceeManagerTest extends BriarTestCase { BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time)); will(returnValue(signBytes)); //noinspection unchecked - oneOf(cryptoComponent).mac(with(samePropertyValuesAs(macKey)), + oneOf(cryptoComponent).mac(with(MAC_LABEL), + with(samePropertyValuesAs(macKey)), with(array(equal(signBytes)))); will(returnValue(mac)); }}); @@ -343,14 +345,15 @@ public class IntroduceeManagerTest extends BriarTestCase { BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time)); will(returnValue(signBytes)); //noinspection unchecked - oneOf(cryptoComponent).mac(with(samePropertyValuesAs(macKey)), + oneOf(cryptoComponent).mac(with(MAC_LABEL), + with(samePropertyValuesAs(macKey)), with(array(equal(signBytes)))); will(returnValue(TestUtils.getRandomBytes(MAC_LENGTH))); }}); try { introduceeManager.verifyMac(state); fail(); - } catch(GeneralSecurityException e) { + } catch (GeneralSecurityException e) { // expected } context.assertIsSatisfied(); diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java index 4902f58c1..f3e3fcf23 100644 --- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java +++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java @@ -56,21 +56,25 @@ import javax.inject.Inject; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID; import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY; +import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL; +import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL; import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY; +import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_LABEL; import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME; import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE; import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY; import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID; +import static org.briarproject.briar.api.introduction.IntroductionConstants.SHARED_SECRET_LABEL; import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE; +import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNING_LABEL; import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME; import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT; import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE; import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST; import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE; -import static org.briarproject.briar.introduction.IntroduceeManager.SIGNING_LABEL_RESPONSE; import static org.briarproject.briar.test.BriarTestUtils.assertGroupCount; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -635,7 +639,7 @@ public class IntroductionIntegrationTest // adapt outgoing message queue to removed message Group g2 = introductionGroupFactory .createIntroductionGroup(contact2From0); - decreaseOutgoingMessageCounter(ch, g2.getId(), 1); + decreaseOutgoingMessageCounter(ch, g2.getId()); // allow visitor to modify response boolean earlyAbort = visitor.visit(response); @@ -746,34 +750,33 @@ public class IntroductionIntegrationTest // create keys KeyPair keyPair1 = crypto.generateSignatureKeyPair(); KeyPair eKeyPair1 = crypto.generateAgreementKeyPair(); - byte[] ePublicKeyBytes1 = eKeyPair1.getPublic().getEncoded(); KeyPair eKeyPair2 = crypto.generateAgreementKeyPair(); - byte[] ePublicKeyBytes2 = eKeyPair2.getPublic().getEncoded(); // Nonce 1 - SecretKey secretKey = - crypto.deriveMasterSecret(ePublicKeyBytes2, eKeyPair1, true); - byte[] nonce1 = crypto.deriveSignatureNonce(secretKey, true); + SecretKey sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, + eKeyPair2.getPublic(), eKeyPair1, true); + byte[] nonce1 = crypto.deriveKeyBindingNonce(ALICE_NONCE_LABEL, + sharedSecret); // Signature 1 - byte[] sig1 = crypto.sign(SIGNING_LABEL_RESPONSE, nonce1, + byte[] sig1 = crypto.sign(SIGNING_LABEL, nonce1, keyPair1.getPrivate().getEncoded()); // MAC 1 - SecretKey macKey1 = crypto.deriveMacKey(secretKey, true); + SecretKey macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret); BdfDictionary tp1 = BdfDictionary.of(new BdfEntry("fake", "fake")); long time1 = clock.currentTimeMillis(); BdfList toMacList = BdfList.of(keyPair1.getPublic().getEncoded(), - ePublicKeyBytes1, tp1, time1); + eKeyPair1.getPublic().getEncoded(), tp1, time1); byte[] toMac = clientHelper.toByteArray(toMacList); - byte[] mac1 = crypto.mac(macKey1, toMac); + byte[] mac1 = crypto.mac(MAC_LABEL, macKey1, toMac); // create only relevant part of state for introducee2 BdfDictionary state = new BdfDictionary(); state.put(PUBLIC_KEY, keyPair1.getPublic().getEncoded()); state.put(TRANSPORT, tp1); state.put(TIME, time1); - state.put(E_PUBLIC_KEY, ePublicKeyBytes1); + state.put(E_PUBLIC_KEY, eKeyPair1.getPublic().getEncoded()); state.put(MAC, mac1); state.put(MAC_KEY, macKey1.getBytes()); state.put(NONCE, nonce1); @@ -786,16 +789,16 @@ public class IntroductionIntegrationTest // replace ephemeral key pair and recalculate matching keys and nonce KeyPair eKeyPair1f = crypto.generateAgreementKeyPair(); byte[] ePublicKeyBytes1f = eKeyPair1f.getPublic().getEncoded(); - secretKey = - crypto.deriveMasterSecret(ePublicKeyBytes2, eKeyPair1f, true); - nonce1 = crypto.deriveSignatureNonce(secretKey, true); + sharedSecret = crypto.deriveSharedSecret(SHARED_SECRET_LABEL, + eKeyPair2.getPublic(), eKeyPair1f, true); + nonce1 = crypto.deriveKeyBindingNonce(ALICE_NONCE_LABEL, sharedSecret); // recalculate MAC - macKey1 = crypto.deriveMacKey(secretKey, true); + macKey1 = crypto.deriveKey(ALICE_MAC_KEY_LABEL, sharedSecret); toMacList = BdfList.of(keyPair1.getPublic().getEncoded(), ePublicKeyBytes1f, tp1, time1); toMac = clientHelper.toByteArray(toMacList); - mac1 = crypto.mac(macKey1, toMac); + mac1 = crypto.mac(MAC_LABEL, macKey1, toMac); // update state with faked information state.put(E_PUBLIC_KEY, ePublicKeyBytes1f); @@ -970,12 +973,12 @@ public class IntroductionIntegrationTest } - private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g, - int num) throws FormatException, DbException { + private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g) + throws FormatException, DbException { BdfDictionary gD = ch.getGroupMetadataAsDictionary(g); LOG.warning(gD.toString()); BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY); - queue.put("nextOut", queue.getLong("nextOut") - num); + queue.put("nextOut", queue.getLong("nextOut") - 1); gD.put(QUEUE_STATE_KEY, queue); ch.mergeGroupMetadata(g, gD); }