summaryrefslogtreecommitdiffstats
path: root/botan/src/entropy/egd/es_egd.cpp
blob: 9e37f8f177b12d0c492be19d751938d87ccbd51a (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
/*
* EGD EntropySource
* (C) 1999-2009 Jack Lloyd
*
* Distributed under the terms of the Botan license
*/

#include <botan/es_egd.h>
#include <botan/parsing.h>
#include <botan/exceptn.h>
#include <cstring>
#include <stdexcept>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/socket.h>
#include <sys/un.h>

#ifndef PF_LOCAL
  #define PF_LOCAL PF_UNIX
#endif

namespace Botan {

EGD_EntropySource::EGD_Socket::EGD_Socket(const std::string& path) :
   socket_path(path), m_fd(-1)
   {
   }

/**
* Attempt a connection to an EGD/PRNGD socket
*/
int EGD_EntropySource::EGD_Socket::open_socket(const std::string& path)
   {
   int fd = ::socket(PF_LOCAL, SOCK_STREAM, 0);

   if(fd >= 0)
      {
      sockaddr_un addr;
      std::memset(&addr, 0, sizeof(addr));
      addr.sun_family = PF_LOCAL;

      if(sizeof(addr.sun_path) < path.length() + 1)
         throw std::invalid_argument("EGD socket path is too long");

      std::strcpy(addr.sun_path, path.c_str());

      int len = sizeof(addr.sun_family) + std::strlen(addr.sun_path) + 1;

      if(::connect(fd, reinterpret_cast<struct ::sockaddr*>(&addr), len) < 0)
         {
         ::close(fd);
         fd = -1;
         }
      }

   return fd;
   }

/**
* Attempt to read entropy from EGD
*/
u32bit EGD_EntropySource::EGD_Socket::read(byte outbuf[], u32bit length)
   {
   if(length == 0)
      return 0;

   if(m_fd < 0)
      {
      m_fd = open_socket(socket_path);
      if(m_fd < 0)
         return 0;
      }

   try
      {
      // 1 == EGD command for non-blocking read
      byte egd_read_command[2] = {
         1, static_cast<byte>(std::min<u32bit>(length, 255)) };

      if(::write(m_fd, egd_read_command, 2) != 2)
         throw std::runtime_error("Writing entropy read command to EGD failed");

      byte out_len = 0;
      if(::read(m_fd, &out_len, 1) != 1)
         throw std::runtime_error("Reading response length from EGD failed");

      if(out_len > egd_read_command[1])
         throw std::runtime_error("Bogus length field recieved from EGD");

      ssize_t count = ::read(m_fd, outbuf, out_len);

      if(count != out_len)
         throw std::runtime_error("Reading entropy result from EGD failed");

      return static_cast<u32bit>(count);
      }
   catch(std::exception)
      {
      this->close();
      // Will attempt to reopen next poll
      }

   return 0;
   }

void EGD_EntropySource::EGD_Socket::close()
   {
   if(m_fd > 0)
      {
      ::close(m_fd);
      m_fd = -1;
      }
   }

/**
* EGD_EntropySource constructor
*/
EGD_EntropySource::EGD_EntropySource(const std::vector<std::string>& paths)
   {
   for(size_t i = 0; i != paths.size(); ++i)
      sockets.push_back(EGD_Socket(paths[i]));
   }

EGD_EntropySource::~EGD_EntropySource()
   {
   for(size_t i = 0; i != sockets.size(); ++i)
      sockets[i].close();
   sockets.clear();
   }

/**
* Gather Entropy from EGD
*/
void EGD_EntropySource::poll(Entropy_Accumulator& accum)
   {
   u32bit go_get = std::min<u32bit>(accum.desired_remaining_bits() / 8, 32);

   MemoryRegion<byte>& io_buffer = accum.get_io_buffer(go_get);

   for(size_t i = 0; i != sockets.size(); ++i)
      {
      u32bit got = sockets[i].read(io_buffer.begin(), io_buffer.size());

      if(got)
         {
         accum.add(io_buffer.begin(), got, 8);
         break;
         }
      }
   }

}