1 /* x509-parser.c -- print human readable info from an X.509 certificate
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include "svn_pools.h"
24 #include "svn_cmdline.h"
25 #include "svn_string.h"
26 #include "svn_dirent_uri.h"
27 #include "svn_io.h"
28 #include "svn_base64.h"
29 #include "svn_x509.h"
30 #include "svn_time.h"
31 
32 #include "svn_private_config.h"
33 
34 #define PEM_BEGIN_CERT "-----BEGIN CERTIFICATE-----"
35 #define PEM_END_CERT "-----END CERTIFICATE-----"
36 
37 static svn_error_t *
38 show_cert(const svn_string_t *der_cert, apr_pool_t *scratch_pool)
39 {
40   svn_x509_certinfo_t *certinfo;
41   const apr_array_header_t *hostnames;
42 
43   SVN_ERR(svn_x509_parse_cert(&certinfo, der_cert->data, der_cert->len,
44                             scratch_pool, scratch_pool));
45 
46   SVN_ERR(svn_cmdline_printf(scratch_pool, _("Subject: %s\n"),
47                              svn_x509_certinfo_get_subject(certinfo, scratch_pool)));
48   SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid from: %s\n"),
49                              svn_time_to_human_cstring(
50                                  svn_x509_certinfo_get_valid_from(certinfo),
51                                  scratch_pool)));
52   SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid until: %s\n"),
53                              svn_time_to_human_cstring(
54                                  svn_x509_certinfo_get_valid_to(certinfo),
55                                  scratch_pool)));
56   SVN_ERR(svn_cmdline_printf(scratch_pool, _("Issuer: %s\n"),
57                              svn_x509_certinfo_get_issuer(certinfo, scratch_pool)));
58   SVN_ERR(svn_cmdline_printf(scratch_pool, _("Fingerprint: %s\n"),
59                              svn_checksum_to_cstring_display(
60                                  svn_x509_certinfo_get_digest(certinfo),
61                                  scratch_pool)));
62 
63   hostnames = svn_x509_certinfo_get_hostnames(certinfo);
64   if (hostnames && !apr_is_empty_array(hostnames))
65     {
66       int i;
67       svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
68       for (i = 0; i < hostnames->nelts; ++i)
69         {
70           const char *hostname = APR_ARRAY_IDX(hostnames, i, const char*);
71           if (i > 0)
72             svn_stringbuf_appendbytes(buf, ", ", 2);
73           svn_stringbuf_appendbytes(buf, hostname, strlen(hostname));
74         }
75       SVN_ERR(svn_cmdline_printf(scratch_pool, _("Hostnames: %s\n"),
76                                  buf->data));
77     }
78 
79   return SVN_NO_ERROR;
80 }
81 
82 static svn_boolean_t
83 is_der_cert(const svn_string_t *raw)
84 {
85   /* really simplistic fingerprinting of a DER.  By definition it must
86    * start with an ASN.1 tag of a constructed (0x20) sequence (0x10).
87    * It's somewhat unfortunate that 0x30 happens to also come out to the
88    * ASCII for '0' which may mean this will create false positives. */
89   return raw->data[0] == 0x30 ? TRUE : FALSE;
90 }
91 
92 static svn_error_t *
93 get_der_cert_from_stream(const svn_string_t **der_cert, svn_stream_t *in,
94                          apr_pool_t *pool)
95 {
96   svn_string_t *raw;
97   SVN_ERR(svn_string_from_stream2(&raw, in, SVN__STREAM_CHUNK_SIZE,
98                                   pool));
99 
100   *der_cert = NULL;
101 
102   /* look for a DER cert */
103   if (is_der_cert(raw))
104     {
105       *der_cert = raw;
106       return SVN_NO_ERROR;
107     }
108   else
109     {
110       const svn_string_t *base64_decoded;
111       const char *start, *end;
112 
113       /* Try decoding as base64 without headers */
114       base64_decoded = svn_base64_decode_string(raw, pool);
115       if (base64_decoded && is_der_cert(base64_decoded))
116         {
117           *der_cert = base64_decoded;
118           return SVN_NO_ERROR;
119         }
120 
121       /* Try decoding as a PEM with begining and ending headers. */
122       start = strstr(raw->data, PEM_BEGIN_CERT);
123       end = strstr(raw->data, PEM_END_CERT);
124       if (start && end && end > start)
125         {
126           svn_string_t *encoded;
127 
128           start += sizeof(PEM_BEGIN_CERT) - 1;
129           end -= 1;
130           encoded = svn_string_ncreate(start, end - start, pool);
131           base64_decoded = svn_base64_decode_string(encoded, pool);
132           if (is_der_cert(base64_decoded))
133             {
134               *der_cert = base64_decoded;
135               return SVN_NO_ERROR;
136             }
137          }
138     }
139 
140   return svn_error_create(SVN_ERR_X509_CERT_INVALID_PEM, NULL,
141                           _("Couldn't find certificate in input data"));
142 }
143 
144 int main (int argc, const char *argv[])
145 {
146   apr_pool_t *pool = NULL;
147   svn_error_t *err;
148   svn_stream_t *in;
149 
150   apr_initialize();
151   atexit(apr_terminate);
152 
153   pool = svn_pool_create(NULL);
154 
155   if (argc == 2)
156     {
157       const char *target = svn_dirent_canonicalize(argv[1], pool);
158       err = svn_stream_open_readonly(&in, target, pool, pool);
159     }
160   else if (argc == 1)
161     {
162       err = svn_stream_for_stdin2(&in, TRUE, pool);
163     }
164   else
165     err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Too many arguments"));
166 
167   if (!err)
168     {
169       const svn_string_t *der_cert;
170       err = get_der_cert_from_stream(&der_cert, in, pool);
171       if (!err)
172         err = show_cert(der_cert, pool);
173     }
174 
175   if (err)
176     return svn_cmdline_handle_exit_error(err, pool, "x509-parser: ");
177 
178   return 0;
179 }
180