aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/neomedia/GatherEntropy.java
blob: b260460edc97e8a7d18d17a91c23964038a06e7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package net.java.sip.communicator.impl.neomedia;

import java.io.*;

import javax.media.*;
import javax.media.control.FormatControl;
import javax.media.format.*;
import javax.media.protocol.*;

import gnu.java.zrtp.utils.*;

import net.java.sip.communicator.impl.neomedia.device.*;
import net.java.sip.communicator.util.Logger;

/**
 * GatherEntropy initializes the Fortuna PRNG with entropy data.
 *
 * GatherEntropy gets the media device configuration and checks which media
 * systems are available. It then reads some data fom media input (capture)
 * devices and uses this data to seed the Fortuna PRNG. The ZrtpFortuna PRNG
 * is a singleton and all other methods that require random data shall use
 * this singleton.
 *
 * Use GatherEntropy during startup and initialization phase of SIP
 * Communicator but after initialization of the media devices to get entropy
 * data at the earliest point. Also make sure that entropy data is read from
 * local sources only and that entropy data is never send out (via networks
 * for example).
 *
 * @author Werner Dittmann <Werner.Dittmann@t-online.de>
 * @author Lubomir Marinov
 */
public class GatherEntropy
{
    /**
     * The <tt>Logger</tt> used by <tt>GatherEntropy</tt>
     * class for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(GatherEntropy.class);

    /**
     * Device config to look for capture devices.
     */
    private final DeviceConfiguration deviceConfiguration;

    /**
     * Other methods shall/may check this to see if Fortuna was seeded with
     * entropy.
     */
    private static boolean entropyOk = false;

    /**
     * Number of gathered entropy bytes.
     */
    private int gatheredEntropy = 0;

    /**
     * How many bytes to gather. This number depends on sample rate, sample
     * size, number of channels and number of audio seconds to use for random
     * data.
     */
    private int bytesToGather = 0;

    /**
     * Bytes per 20ms time slice.
     */
    private int bytes20ms = 0;

    /**
     * How many seconds of audio to read.
     *
     */
    private static final int NUM_OF_SECONDS = 2;

    public GatherEntropy(DeviceConfiguration deviceConfiguration)
    {
        this.deviceConfiguration = deviceConfiguration;
    }

    /**
     * Get status of entropy flag.
     *
     * @return Status if entropy was gathered and set in Fortuna PRNG.
     */
    public static boolean isEntropyOk()
    {
        return entropyOk;
    }

    /**
     * @return the number of gathered entropy bytes.
     */
    protected int getGatheredEntropy()
    {
        return gatheredEntropy;
    }
    /**
     * Set entropy to ZrtpFortuna singleton.
     *
     * The methods reads entropy data and seeds the ZrtpFortuna singleton.
     * The methods seeds the first pool (0) of Fortuna to make sure that
     * this entropy is always used.
     *
     * @return true if entropy data was available, false otherwise.
     */
    public boolean setEntropy()
    {
        boolean retValue = false;
        GatherAudio gatherer = new GatherAudio();
        retValue = gatherer.prepareAudioEntropy();
        if (retValue)
            gatherer.start();
        return retValue;
    }

    private class GatherAudio extends Thread implements BufferTransferHandler
    {
        /**
         * The PortAudio <tt>DataSource</tt> which provides
         * {@link #audioStream}.
         */
        private DataSource dataSource = null;

        /**
         * The <tt>PortAudioStream</tt> from which audio data is captured.
         */
        private SourceStream audioStream = null;

        /**
         * The next three elements control the push buffer that Javasound
         * uses.
         */
        private final Buffer firstBuf = new Buffer();
        private boolean bufferAvailable = false;
        private final Object bufferSync = new Object();

        /**
         * Prepares to read entropy data from portaudio capture device.
         *
         * The method gets an PortAudio instance with a set of capture
         * parameters.
         *
         * @return True if the PortAudio input stream is available.
         */
        private boolean prepareAudioEntropy()
        {
            CaptureDeviceInfo audioCaptureDevice =
                    deviceConfiguration.getAudioCaptureDevice();
            if (audioCaptureDevice == null)
                return false;

            MediaLocator audioCaptureDeviceLocator
                = audioCaptureDevice.getLocator();

            if (audioCaptureDeviceLocator == null)
                return false;

            try
            {
                dataSource = Manager.createDataSource(audioCaptureDeviceLocator);
            }
            catch (NoDataSourceException e)
            {
                logger.warn("No data source during entropy preparation", e);
                return false;
            }
            catch (IOException e)
            {
                logger.warn("Got an IO Exception during entropy preparation", e);
                return false;
            }
            FormatControl fc = ((CaptureDevice)dataSource).getFormatControls()[0];
            AudioFormat af = (AudioFormat)fc.getFormat();
            int framesToRead = (int)(af.getSampleRate() * NUM_OF_SECONDS);
            int frameSize = (af.getSampleSizeInBits() / 8) * af.getChannels();
            bytesToGather = framesToRead * frameSize;
            bytes20ms = frameSize * (int)(af.getSampleRate() /50);

            if (dataSource instanceof PullBufferDataSource)
            {
                audioStream = ((PullBufferDataSource) dataSource).getStreams()[0];
            }
            else
            {
                audioStream = ((PushBufferDataSource) dataSource).getStreams()[0];
                ((PushBufferStream)audioStream).setTransferHandler(this);
            }
            return (audioStream != null);
        }

        public void transferData(PushBufferStream stream)
        {
            try
            {
                stream.read(firstBuf);
            }
            catch (IOException e)
            {
                logger.warn("Got IOException during transfer data", e);
            }
            synchronized (bufferSync)
            {
                bufferAvailable = true;
                bufferSync.notifyAll();
            }
        }
        /**
         * Gather entropy from portaudio capture device and seed Fortuna PRNG.
         *
         * The method gathers a number of samples and seeds the Fortuna PRNG.
         */
        @Override
        public void run()
        {
            ZrtpFortuna fortuna = ZrtpFortuna.getInstance();

            if ((dataSource == null) || (audioStream == null))
                return;

            try
            {
                dataSource.start();

                int i = 0;
                while (gatheredEntropy < bytesToGather)
                {
                    if (audioStream instanceof PushBufferStream)
                    {
                        synchronized (bufferSync)
                        {
                            while (!bufferAvailable)
                            {
                                try
                                {
                                    bufferSync.wait();
                                }
                                catch (InterruptedException e)
                                {
                                    // ignore
                                }
                            }
                            bufferAvailable = false;
                        }
                    }
                    else
                        ((PullBufferStream)audioStream).read(firstBuf);
                    byte[] entropy = (byte[])firstBuf.getData();
                    gatheredEntropy += entropy.length;

                    // distribute first buffers evenly over the pools, put
                    // others on the first pools. This method is adapted to
                    // SC requirements to get random data
                    if (i < 32)
                        fortuna.addSeedMaterial(entropy);
                    else
                    {
                        fortuna
                            .addSeedMaterial((i%3), entropy, 0, entropy.length);
                    }
                    i = gatheredEntropy / bytes20ms;
                }
                entropyOk = true;
                if (logger.isInfoEnabled())
                    logger.info("GatherEntropy got: " + gatheredEntropy + " bytes");
            }
            catch (IOException ioex)
            {
                // ignore exception
            }
            finally
            {
                audioStream = null;
                dataSource.disconnect();
            }
            // this forces a Fortuna to use the new seed (entropy) data.
            byte[] random = new byte[300];
            fortuna.nextBytes(random);
        }
    }
}