aboutsummaryrefslogtreecommitdiff
path: root/networking/sslgetcert.c
diff options
context:
space:
mode:
Diffstat (limited to 'networking/sslgetcert.c')
-rw-r--r--networking/sslgetcert.c762
1 files changed, 762 insertions, 0 deletions
diff --git a/networking/sslgetcert.c b/networking/sslgetcert.c
new file mode 100644
index 0000000..a0fbf3c
--- /dev/null
+++ b/networking/sslgetcert.c
@@ -0,0 +1,762 @@
+/* Simple SSL client to get server certificates */
+
+#include "libbb.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#define OPT_4 (1 << 0)
+#define OPT_6 (1 << 1)
+
+#define MSG_HANDSHAKE 22
+#define HS_CLIENT_HELLO 1
+#define HS_SERVER_HELLO 2
+#define HS_CERTIFICATE 11
+
+struct buf
+{
+ size_t offset;
+ size_t size;
+ size_t maxsize;
+ char *buf;
+ int fd;
+};
+
+#define BUF_CHUNK 4096
+
+struct msgbuf
+{
+ struct buf *inbuf;
+ struct buf *outbuf;
+
+ struct buf buffer;
+};
+
+struct hsbuf
+{
+ struct buf buffer;
+};
+
+#define URANDOM_DEV "/dev/urandom"
+
+#define DBQ(str) "\"" #str "\""
+
+static int tcp_fd= -1;
+
+static void fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "ssltestc30: ");
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+static void got_alarm(int sig __attribute__((unused)) )
+{
+ if (tcp_fd != -1)
+ fcntl(tcp_fd, F_SETFL, fcntl(tcp_fd, F_GETFL) | O_NONBLOCK);
+ alarm(1);
+}
+
+static void buf_init(struct buf *buf, int fd)
+{
+ buf->maxsize= 0;
+ buf->size= 0;
+ buf->offset= 0;
+ buf->buf= NULL;
+ buf->fd= fd;
+}
+
+static void buf_add(struct buf *buf, const void *data, size_t len)
+{
+ size_t maxsize;
+ void *newbuf;
+
+ if (buf->size+len <= buf->maxsize)
+ {
+ /* Easy case, just add data */
+ memcpy(buf->buf+buf->size, data, len);
+ buf->size += len;
+ return;
+ }
+
+ /* Just get a new buffer */
+ maxsize= buf->size-buf->offset + len + BUF_CHUNK;
+ newbuf= malloc(maxsize);
+ if (!newbuf)
+ {
+ fprintf(stderr, "unable to allocate %ld bytes\n", maxsize);
+ exit(1);
+ }
+
+ if (buf->offset < buf->size)
+ {
+ /* Copy existing data */
+ memcpy(newbuf, buf->buf+buf->offset, buf->size-buf->offset);
+ buf->size -= buf->offset;
+ buf->offset= 0;
+ }
+ else
+ {
+ buf->size= buf->offset= 0;
+ }
+ buf->maxsize= maxsize;
+ free(buf->buf);
+ buf->buf= newbuf;
+
+ memcpy(buf->buf+buf->size, data, len);
+ buf->size += len;
+}
+
+static void buf_add_b64(struct buf *buf, void *data, size_t len)
+{
+ char b64[]=
+ "ABCDEFGHIJKLMNOP"
+ "QRSTUVWXYZabcdef"
+ "ghijklmnopqrstuv"
+ "wxyz0123456789+/";
+ int i;
+ uint8_t *p;
+ uint32_t v;
+ char str[4];
+
+ p= data;
+
+ for (i= 0; i+3 <= len; i += 3, p += 3)
+ {
+ v= (p[0] << 16) + (p[1] << 8) + p[2];
+ str[0]= b64[(v >> 18) & 63];
+ str[1]= b64[(v >> 12) & 63];
+ str[2]= b64[(v >> 6) & 63];
+ str[3]= b64[(v >> 0) & 63];
+ buf_add(buf, str, 4);
+ if (i % 48 == 45)
+ buf_add(buf, "\n", 1);
+ }
+ switch(len-i)
+ {
+ case 0: break; /* Nothing to do */
+ case 1:
+ v= (p[0] << 16);
+ str[0]= b64[(v >> 18) & 63];
+ str[1]= b64[(v >> 12) & 63];
+ str[2]= '=';
+ str[3]= '=';
+ buf_add(buf, str, 4);
+ break;
+ case 2:
+ v= (p[0] << 16) + (p[1] << 8);
+ str[0]= b64[(v >> 18) & 63];
+ str[1]= b64[(v >> 12) & 63];
+ str[2]= b64[(v >> 6) & 63];
+ str[3]= '=';
+ buf_add(buf, str, 4);
+ break;
+ default:
+ fatal("bad state in buf_add_b64");
+ }
+}
+
+static int buf_read(struct buf *buf)
+{
+ int r;
+ size_t maxsize;
+ void *newbuf;
+
+ if (buf->size >= buf->maxsize)
+ {
+ if (buf->size-buf->offset + BUF_CHUNK <= buf->maxsize)
+ {
+ /* The buffer is big enough, just need to compact */
+ fatal("buf_read: should compact");
+ }
+ else
+ {
+ maxsize= buf->size-buf->offset + BUF_CHUNK;
+ newbuf= malloc(maxsize);
+ if (!newbuf)
+ fatal("unable to allocate %d bytes", maxsize);
+ buf->maxsize= maxsize;
+
+ if (buf->size > buf->offset)
+ {
+ memcpy(newbuf, buf->buf+buf->offset,
+ buf->size-buf->offset);
+ buf->size -= buf->offset;
+ buf->offset= 0;
+ }
+ else
+ {
+ buf->size= buf->offset= 0;
+ }
+ free(buf->buf);
+ buf->buf= newbuf;
+ }
+ }
+
+ for (;;)
+ {
+ r= read(buf->fd, buf->buf+buf->size, buf->maxsize-buf->size);
+ if (r > 0)
+ {
+ buf->size += r;
+ break;
+ }
+ fprintf(stderr, "read error on fd %d: %s",
+ buf->fd, r == 0 ? "eof" : strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int buf_write(struct buf *buf)
+{
+ int r;
+ size_t len;
+
+ while (buf->offset < buf->size)
+ {
+ len= buf->size - buf->offset;
+ r= write(buf->fd, buf->buf+buf->offset, len);
+ if (len > 0)
+ {
+ buf->offset += len;
+ continue;
+ }
+ fprintf(stderr, "write to %d failed: %s\n",
+ buf->fd, r == 0 ? "eof" : strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static void buf_cleanup(struct buf *buf)
+{
+ free(buf->buf);
+ buf->offset= buf->size= buf->maxsize= 0;
+}
+
+static void msgbuf_init(struct msgbuf *msgbuf,
+ struct buf *inbuf, struct buf *outbuf)
+{
+ buf_init(&msgbuf->buffer, -1);
+ msgbuf->inbuf= inbuf;
+ msgbuf->outbuf= outbuf;
+}
+
+static void msgbuf_add(struct msgbuf *msgbuf, void *buf, size_t size)
+{
+ buf_add(&msgbuf->buffer, buf, size);
+}
+
+static int Xmsgbuf_read(struct msgbuf *msgbuf, int type)
+{
+ int r;
+ size_t len;
+ uint8_t *p;
+
+ for(;;)
+ {
+ while (msgbuf->inbuf->size - msgbuf->inbuf->offset < 5)
+ {
+ r= buf_read(msgbuf->inbuf);
+ if (r < 0)
+ {
+ fprintf(stderr,
+ "msgbuf_read: buf_read failed\n");
+ return -1;
+ }
+ continue;
+ }
+ p= (uint8_t *)msgbuf->inbuf->buf+msgbuf->inbuf->offset;
+ type= p[0];
+ if (p[0] != type)
+ {
+ fprintf(stderr, "msgbuf_read: got type %d\n", p[0]);
+ return -1;
+ }
+ if (p[1] != 3 || p[2] != 0)
+ {
+ fprintf(stderr,
+ "msgbuf_read: got bad major/minor %d.%d\n",
+ p[1], p[2]);
+ return -1;
+ }
+ len= (p[3] << 8) + p[4];
+ if (msgbuf->inbuf->size - msgbuf->inbuf->offset < 5 + len)
+ {
+ r= buf_read(msgbuf->inbuf);
+ if (r < 0)
+ {
+ fprintf(stderr,
+ "msgbuf_read: buf_read failed\n");
+ return -1;
+ }
+ continue;
+ }
+
+ /* Move the data to the msg buffer */
+ msgbuf_add(msgbuf, msgbuf->inbuf->buf+msgbuf->inbuf->offset+5,
+ len);
+ msgbuf->inbuf->offset += 5+len;
+ break;
+ }
+ return 0;
+}
+
+static void msgbuf_final(struct msgbuf *msgbuf, int type)
+{
+ uint8_t c;
+ size_t len;
+
+ while (msgbuf->buffer.offset < msgbuf->buffer.size)
+ {
+ len= msgbuf->buffer.size-msgbuf->buffer.offset;
+ if (len > 0x4000)
+ len= 0x4000;
+
+ c= type;
+ buf_add(msgbuf->outbuf, &c, 1);
+
+ c= 3;
+ buf_add(msgbuf->outbuf, &c, 1);
+
+ c= 0;
+ buf_add(msgbuf->outbuf, &c, 1);
+
+ c= len >> 8;
+ buf_add(msgbuf->outbuf, &c, 1);
+
+ c= len;
+ buf_add(msgbuf->outbuf, &c, 1);
+
+ buf_add(msgbuf->outbuf,
+ msgbuf->buffer.buf + msgbuf->buffer.offset, len);
+
+ msgbuf->buffer.offset += len;
+ }
+}
+
+static void msgbuf_cleanup(struct msgbuf *msgbuf)
+{
+ buf_cleanup(&msgbuf->buffer);
+}
+
+static void hsbuf_init(struct hsbuf *hsbuf)
+{
+ buf_init(&hsbuf->buffer, -1);
+}
+
+static void hsbuf_add(struct hsbuf *hsbuf, const void *buf, size_t len)
+{
+ buf_add(&hsbuf->buffer, buf, len);
+}
+
+static void hsbuf_cleanup(struct hsbuf *hsbuf)
+{
+ buf_cleanup(&hsbuf->buffer);
+}
+
+static void hsbuf_final(struct hsbuf *hsbuf, int type, struct msgbuf *msgbuf)
+{
+ uint8_t c;
+ size_t len;
+
+ len= hsbuf->buffer.size - hsbuf->buffer.offset;
+
+ c= type;
+ msgbuf_add(msgbuf, &c, 1);
+
+ c= (len >> 16);
+ msgbuf_add(msgbuf, &c, 1);
+
+ c= (len >> 8);
+ msgbuf_add(msgbuf, &c, 1);
+
+ c= len;
+ msgbuf_add(msgbuf, &c, 1);
+
+ msgbuf_add(msgbuf, hsbuf->buffer.buf + hsbuf->buffer.offset, len);
+ hsbuf->buffer.offset += len;
+}
+
+static int connect_to_name(int af, const char *host, const char *port)
+{
+ int r, s, s_errno;
+ struct addrinfo *res, *aip;
+ struct addrinfo hints;
+
+ memset(&hints, '\0', sizeof(hints));
+ hints.ai_family= af;
+ hints.ai_socktype= SOCK_STREAM;
+ r= getaddrinfo(host, port, &hints, &res);
+ if (r != 0)
+ {
+ fprintf(stderr,
+ "unable to resolve '%s': %s", host, gai_strerror(r));
+ return -1;
+ }
+
+ s_errno= 0;
+ s= -1;
+ for (aip= res; aip != NULL; aip= aip->ai_next)
+ {
+ s= socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s == -1)
+ {
+ s_errno= errno;
+ continue;
+ }
+
+ if (connect(s, res->ai_addr, res->ai_addrlen) == 0)
+ break;
+
+ s_errno= errno;
+ close(s);
+ s= -1;
+ }
+
+ freeaddrinfo(res);
+ if (s == -1)
+ errno= s_errno;
+ return s;
+}
+
+static void add_random(struct hsbuf *hsbuf)
+{
+ int fd;
+ time_t t;
+ uint8_t buf[32];
+
+ t= time(NULL);
+ buf[0]= t >> 24;
+ buf[1]= t >> 16;
+ buf[2]= t >> 8;
+ buf[3]= t;
+
+ fd= open(URANDOM_DEV, O_RDONLY);
+
+ /* Best effort, just ignore errors */
+ if (fd != -1)
+ {
+ read(fd, buf+4, sizeof(buf)-4);
+ close(fd);
+ }
+ hsbuf_add(hsbuf, buf, sizeof(buf));
+}
+
+static void add_sessionid(struct hsbuf *hsbuf)
+{
+ uint8_t c;
+
+ c= 0;
+ hsbuf_add(hsbuf, &c, 1);
+}
+
+static void add_ciphers(struct hsbuf *hsbuf)
+{
+ uint8_t c;
+ size_t len;
+ uint8_t ciphers[]= { 0x0,0xff, 0x0,0x88, 0x0,0x87, 0x0,0x39, 0x0,0x38,
+ 0x0,0x84, 0x0,0x35, 0x0,0x45, 0x0,0x44, 0x0,0x33, 0x0,0x32,
+ 0x0,0x96, 0x0,0x41, 0x0,0x4, 0x0,0x5, 0x0,0x2f, 0x0,0x16,
+ 0x0,0x13, 0xfe,0xff, 0x0,0xa };
+
+ len= sizeof(ciphers);
+
+ c= len >> 8;
+ hsbuf_add(hsbuf, &c, 1);
+ c= len;
+ hsbuf_add(hsbuf, &c, 1);
+
+ hsbuf_add(hsbuf, ciphers, len);
+}
+
+static void add_compression(struct hsbuf *hsbuf)
+{
+ uint8_t c;
+ size_t len;
+ uint8_t compression[]= { 0x1, 0x0 };
+
+ len= sizeof(compression);
+
+ c= len;
+ hsbuf_add(hsbuf, &c, 1);
+
+ hsbuf_add(hsbuf, compression, len);
+}
+
+static int Xeat_server_hello(struct msgbuf *msgbuf)
+{
+ int r;
+ size_t len;
+ uint8_t *p;
+
+ for (;;)
+ {
+ if (msgbuf->buffer.size - msgbuf->buffer.offset < 4)
+ {
+ r= Xmsgbuf_read(msgbuf, MSG_HANDSHAKE);
+ if (r < 0)
+ {
+ fprintf(stderr,
+ "eat_server_hello: msgbuf_read failed\n");
+ return -1;
+
+ }
+ continue;
+ }
+ p= (uint8_t *)msgbuf->buffer.buf+msgbuf->buffer.offset;
+ if (p[0] != HS_SERVER_HELLO)
+ {
+ fprintf(stderr, "eat_server_hello: got type %d\n",
+ p[0]);
+ return -1;
+ }
+ len= (p[1] << 16) + (p[2] << 8) + p[3];
+ if (msgbuf->buffer.size - msgbuf->buffer.offset < 4+len)
+ {
+ r= Xmsgbuf_read(msgbuf, MSG_HANDSHAKE);
+ if (r < 0)
+ {
+ fprintf(stderr,
+ "eat_server_hello: msgbuf_read failed\n");
+ return -1;
+ }
+ continue;
+ }
+ msgbuf->buffer.offset += 4+len;
+ break;
+ }
+ return 0;
+}
+
+static int Xeat_certificate(struct msgbuf *msgbuf)
+{
+ int i, n, r, first, slen;
+ size_t o, len;
+ uint8_t *p;
+ struct buf tmpbuf;
+
+ for (;;)
+ {
+ if (msgbuf->buffer.size - msgbuf->buffer.offset < 4)
+ {
+ r= Xmsgbuf_read(msgbuf, MSG_HANDSHAKE);
+ if (r < 0)
+ {
+ fprintf(stderr,
+ "eat_certificate: msgbuf_read failed\n");
+ return -1;
+ }
+ continue;
+ }
+ p= (uint8_t *)msgbuf->buffer.buf+msgbuf->buffer.offset;
+ if (p[0] != HS_CERTIFICATE)
+ {
+ fprintf(stderr, "eat_certificate: got type %d\n", p[0]);
+ return -1;
+ }
+ len= (p[1] << 16) + (p[2] << 8) + p[3];
+ if (msgbuf->buffer.size - msgbuf->buffer.offset < 4+len)
+ {
+ r= Xmsgbuf_read(msgbuf, MSG_HANDSHAKE);
+ if (r < 0)
+ {
+ fprintf(stderr,
+ "eat_certificate: msgbuf_read failed\n");
+ return -1;
+ }
+ continue;
+ }
+ p += 4;
+ n= (p[0] << 16) + (p[1] << 8) + p[2];
+ o= 3;
+ first= 1;
+ printf(", " DBQ(cert) ":[ ");
+
+ buf_init(&tmpbuf, -1);
+ while (o < 3+n)
+ {
+ slen= (p[o] << 16) + (p[o+1] << 8) + p[o+2];
+ buf_add_b64(&tmpbuf, p+o+3, slen);
+ printf("%s\"-----BEGIN CERTIFICATE-----\\n",
+ !first ? ", " : "");
+ for (i= tmpbuf.offset; i<tmpbuf.size; i++)
+ {
+ if (tmpbuf.buf[i] == '\n')
+ fputs("\\n", stdout);
+ else
+ putchar(tmpbuf.buf[i]);
+ }
+ printf("\\n-----END CERTIFICATE-----\"");
+ tmpbuf.size= tmpbuf.offset;
+ o += 3+slen;
+ first= 0;
+ }
+ buf_cleanup(&tmpbuf);
+ printf(" ]");
+ if (o != 3+n)
+ {
+ fprintf(stderr,
+ "do_certificate: bad amount of cert data\n");
+ return -1;
+ }
+ if (o != len)
+ {
+ fprintf(stderr,
+ "do_certificate: bad amount of cert data\n");
+ return -1;
+ }
+ msgbuf->buffer.offset += 4+len;
+ break;
+ }
+
+ return 0;
+}
+
+extern int get_atlas_fw_version(void); /* In eperd */
+
+int sslgetcert_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int sslgetcert_main(int argc UNUSED_PARAM, char **argv)
+{
+ int r, af;
+ uint32_t opt;
+ socklen_t salen;
+ char *str_Atlas;
+ const char *str_port;
+ char *hostname;
+ struct buf inbuf, outbuf;
+ struct msgbuf msginbuf, msgoutbuf;
+ struct hsbuf hsbuf;
+ struct sockaddr_storage sa;
+ struct sigaction sia;
+ char hostbuf[NI_MAXHOST];
+
+ str_Atlas= NULL;
+ str_port= "https";
+
+ opt_complementary = "=1";
+ opt = getopt32(argv, "!46A:p:", &str_Atlas, &str_port);
+ if (opt == (uint32_t)-1)
+ return 1;
+ hostname = argv[optind];
+
+ af= AF_UNSPEC;
+ if (opt & OPT_4)
+ af= AF_INET;
+ if (opt & OPT_6)
+ af= AF_INET6;
+
+ sia.sa_flags= 0;
+ sia.sa_handler= got_alarm;
+ sigemptyset(&sia.sa_mask);
+ sigaction(SIGALRM, &sia, NULL);
+ alarm(10);
+ signal(SIGPIPE, SIG_IGN);
+
+ tcp_fd= connect_to_name(af, hostname, str_port);
+
+ printf("RESULT { ");
+ if (str_Atlas)
+ {
+ printf(DBQ(id) ":" DBQ(%s)
+ ", " DBQ(fw) ":%d",
+ str_Atlas, get_atlas_fw_version());
+ }
+
+ printf("%s" DBQ(time) ":%ld", str_Atlas ? ", " : "", time(NULL));
+ printf(", " DBQ(dst_name) ":" DBQ(%s) ", " DBQ(dst_port) ":" DBQ(%s),
+ hostname, str_port);
+
+ printf(", " DBQ(method) ":" DBQ(SSL) ", " DBQ(ver) ":" DBQ(3.0));
+ if (af != AF_UNSPEC)
+ printf(", " DBQ(af) ": %d", af == AF_INET6 ? 6 : 4);
+
+ if (tcp_fd == -1)
+ {
+ printf(", " DBQ(err) ":" DBQ(unable to connect) " }\n");
+ return 0;
+ }
+
+ salen= sizeof(sa);
+ if (getpeername(tcp_fd, (struct sockaddr *)&sa, &salen) != -1)
+ {
+ getnameinfo((struct sockaddr *)&sa, salen,
+ hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NUMERICHOST);
+ printf(", " DBQ(dst_addr) ":" DBQ(%s), hostbuf);
+ if (af == AF_UNSPEC)
+ {
+ printf(", " DBQ(af) ": %d",
+ sa.ss_family == AF_INET6 ? 6 : 4);
+ }
+ }
+ salen= sizeof(sa);
+ if (getsockname(tcp_fd, (struct sockaddr *)&sa, &salen) != -1)
+ {
+ getnameinfo((struct sockaddr *)&sa, salen,
+ hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NUMERICHOST);
+ printf(", " DBQ(src_addr) ":" DBQ(%s), hostbuf);
+ }
+
+ buf_init(&outbuf, tcp_fd);
+ msgbuf_init(&msgoutbuf, NULL, &outbuf);
+ hsbuf_init(&hsbuf);
+
+ /* Major/minor */
+ hsbuf_add(&hsbuf, "\3", 1);
+ hsbuf_add(&hsbuf, "\0", 1);
+ add_random(&hsbuf);
+ add_sessionid(&hsbuf);
+ add_ciphers(&hsbuf);
+ add_compression(&hsbuf);
+
+ hsbuf_final(&hsbuf, HS_CLIENT_HELLO, &msgoutbuf);
+ msgbuf_final(&msgoutbuf, MSG_HANDSHAKE);
+ r= buf_write(&outbuf);
+ if (r == -1)
+ goto fail;
+
+ buf_init(&inbuf, tcp_fd);
+ msgbuf_init(&msginbuf, &inbuf, NULL);
+
+ if (Xeat_server_hello(&msginbuf) < 0)
+ goto fail;
+
+ if (Xeat_certificate(&msginbuf) < 0)
+ goto fail;
+
+fail:
+ printf(" }\n");
+
+ close(tcp_fd);
+ tcp_fd= -1;
+
+ hsbuf_cleanup(&hsbuf);
+ msgbuf_cleanup(&msginbuf);
+ msgbuf_cleanup(&msgoutbuf);
+ buf_cleanup(&inbuf);
+ buf_cleanup(&outbuf);
+
+ alarm(0);
+ signal(SIGPIPE, SIG_DFL);
+
+ return 0;
+}