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 *
show_cert(const svn_string_t * der_cert,apr_pool_t * scratch_pool)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
is_der_cert(const svn_string_t * raw)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 *
get_der_cert_from_stream(const svn_string_t ** der_cert,svn_stream_t * in,apr_pool_t * pool)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
main(int argc,const char * argv[])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