]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java
21c8aa960b76d1d13344f3225c617b4f948d61cd
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / util / KeyUtils.java
1 package org.asamk.signal.manager.util;
2
3 import org.asamk.signal.manager.storage.SignalAccount;
4 import org.signal.libsignal.protocol.IdentityKey;
5 import org.signal.libsignal.protocol.IdentityKeyPair;
6 import org.signal.libsignal.protocol.InvalidKeyException;
7 import org.signal.libsignal.protocol.ecc.Curve;
8 import org.signal.libsignal.protocol.ecc.ECPrivateKey;
9 import org.signal.libsignal.protocol.kem.KEMKeyPair;
10 import org.signal.libsignal.protocol.kem.KEMKeyType;
11 import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
12 import org.signal.libsignal.protocol.state.PreKeyRecord;
13 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
14 import org.signal.libsignal.zkgroup.InvalidInputException;
15 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
16 import org.whispersystems.signalservice.api.account.PreKeyCollection;
17 import org.whispersystems.signalservice.api.backup.MediaRootBackupKey;
18 import org.whispersystems.signalservice.api.kbs.MasterKey;
19
20 import java.security.SecureRandom;
21 import java.util.ArrayList;
22 import java.util.Base64;
23 import java.util.List;
24
25 import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_BATCH_SIZE;
26 import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
27
28 public class KeyUtils {
29
30 private static final SecureRandom secureRandom = new SecureRandom();
31
32 private KeyUtils() {
33 }
34
35 public static IdentityKeyPair getIdentityKeyPair(byte[] publicKeyBytes, byte[] privateKeyBytes) {
36 try {
37 IdentityKey publicKey = new IdentityKey(publicKeyBytes);
38 ECPrivateKey privateKey = Curve.decodePrivatePoint(privateKeyBytes);
39
40 return new IdentityKeyPair(publicKey, privateKey);
41 } catch (InvalidKeyException e) {
42 throw new AssertionError(e);
43 }
44 }
45
46 public static IdentityKeyPair generateIdentityKeyPair() {
47 var djbKeyPair = Curve.generateKeyPair();
48 var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
49 var djbPrivateKey = djbKeyPair.getPrivateKey();
50
51 return new IdentityKeyPair(djbIdentityKey, djbPrivateKey);
52 }
53
54 public static List<PreKeyRecord> generatePreKeyRecords(final int offset) {
55 var records = new ArrayList<PreKeyRecord>(PREKEY_BATCH_SIZE);
56 for (var i = 0; i < PREKEY_BATCH_SIZE; i++) {
57 var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID;
58 var keyPair = Curve.generateKeyPair();
59 var record = new PreKeyRecord(preKeyId, keyPair);
60
61 records.add(record);
62 }
63 return records;
64 }
65
66 public static SignedPreKeyRecord generateSignedPreKeyRecord(
67 final int signedPreKeyId,
68 final ECPrivateKey privateKey
69 ) {
70 var keyPair = Curve.generateKeyPair();
71 byte[] signature;
72 try {
73 signature = Curve.calculateSignature(privateKey, keyPair.getPublicKey().serialize());
74 } catch (InvalidKeyException e) {
75 throw new AssertionError(e);
76 }
77 return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
78 }
79
80 public static List<KyberPreKeyRecord> generateKyberPreKeyRecords(final int offset, final ECPrivateKey privateKey) {
81 var records = new ArrayList<KyberPreKeyRecord>(PREKEY_BATCH_SIZE);
82 for (var i = 0; i < PREKEY_BATCH_SIZE; i++) {
83 var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID;
84 records.add(generateKyberPreKeyRecord(preKeyId, privateKey));
85 }
86 return records;
87 }
88
89 public static KyberPreKeyRecord generateKyberPreKeyRecord(final int preKeyId, final ECPrivateKey privateKey) {
90 KEMKeyPair keyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024);
91 byte[] signature = privateKey.calculateSignature(keyPair.getPublicKey().serialize());
92
93 return new KyberPreKeyRecord(preKeyId, System.currentTimeMillis(), keyPair, signature);
94 }
95
96 public static ProfileKey createProfileKey() {
97 try {
98 return new ProfileKey(getSecretBytes(32));
99 } catch (InvalidInputException e) {
100 throw new AssertionError("Profile key is guaranteed to be 32 bytes here");
101 }
102 }
103
104 public static String createPassword() {
105 return getSecret(18);
106 }
107
108 public static byte[] createStickerUploadKey() {
109 return getSecretBytes(32);
110 }
111
112 public static MasterKey createMasterKey() {
113 return MasterKey.createNew(secureRandom);
114 }
115
116 public static MediaRootBackupKey createMediaRootBackupKey() {
117 return new MediaRootBackupKey(getSecretBytes(32));
118 }
119
120 public static byte[] createRawStorageId() {
121 return getSecretBytes(16);
122 }
123
124 private static String getSecret(int size) {
125 var secret = getSecretBytes(size);
126 return Base64.getEncoder().encodeToString(secret);
127 }
128
129 public static byte[] getSecretBytes(int size) {
130 var secret = new byte[size];
131 secureRandom.nextBytes(secret);
132 return secret;
133 }
134
135 public static int getRandomInt(int bound) {
136 return secureRandom.nextInt(bound);
137 }
138
139 public static PreKeyCollection generatePreKeysForType(final SignalAccount.AccountData<?> accountData) {
140 final var keyPair = accountData.getIdentityKeyPair();
141 final var preKeyMetadata = accountData.getPreKeyMetadata();
142
143 final var nextSignedPreKeyId = preKeyMetadata.getNextSignedPreKeyId();
144 final var signedPreKey = generateSignedPreKeyRecord(nextSignedPreKeyId, keyPair.getPrivateKey());
145
146 final var privateKey = keyPair.getPrivateKey();
147 final var kyberPreKeyIdOffset = preKeyMetadata.getNextKyberPreKeyId();
148 final var lastResortKyberPreKey = generateKyberPreKeyRecord(kyberPreKeyIdOffset, privateKey);
149
150 return new PreKeyCollection(keyPair.getPublicKey(), signedPreKey, lastResortKyberPreKey);
151 }
152 }