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