#!/usr/bin/env lua --[[ *************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * *************************************************************************** * * This Lua implementation written (2010) by Jeff Pohlmeyer, adapted from * the Perl script "mk-ca-bundle.pl" by Daniel Stenberg. * *************************************************************************** * * The base64 encoding routine contained herein was written by Diego Nehab * and released under the following license: * * Copyright (C) 2000 TeCGraf, PUC-Rio. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. *************************************************************************** --]] local crt='ca-bundle.crt' -- Default output file -- Remote file local host = 'http://mxr.mozilla.org/' local path = 'seamonkey/source/security/nss/lib/ckfw/builtins/' local certdata_txt = 'certdata.txt' local query='?raw=1' local url=host..path..certdata_txt..query -- Command line options local do_download=true local make_backup=false local delete_certdata=false local show_help=false local use_comments=true local polarssl_workaround=false local verbose=false -- Enironment options -- Left-hand wrap margin for base64 data local wrap_margin=tonumber(os.getenv("CERTWRAP")) or 76 -- Maximum number of certs to process, zero means no limit local maxcerts=tonumber(os.getenv("MAXCERTS")) or 0 -- Parse command line options for i,a in ipairs(arg) do if a=='-h' then show_help=true elseif a=='-n' then do_download=false elseif a=='-b' then make_backup=true elseif a=='-u' then delete_certdata=true elseif a=='-s' then use_comments=false elseif a=='-p' then polarssl_workaround=true elseif a=='-v' then verbose=true elseif i==#arg and not a:find("^%-") then crt=a --filename must be final arg else do io.stderr:write("Unknown option: ",a,"\n") io.stderr:write("Try: `",arg[0]," -h'\n") os.exit(1) end end end if show_help then io.write( [[ Usage: ]] ..arg[0].. [[ [-b] [-n] [-u] [] -b backup an existing version of ca-bundle.crt -n no download of certdata.txt (to use existing) -u unlink (remove) certdata.txt after processing -p enable polarssl signature algorithm workaround -v be verbose and print out processed CAs -s strip all comments Environment options: CERTWRAP=76 : Left-hand wrap margin for base64 data MAXCERTS=0 : Maximum number of certs to output, zero=unlimited ]] ) os.exit(0) end --[[ PolarSSL only supports a limited number of signature algorithms. This test might be overly strict, but at least it catches the "ecdsa-with-SHA384" that was causing me problems. --]] function check_sig_algo(caname, certnum, data) if not polarssl_workaround then return true end function algo_fail(id) io.stderr:write( '=> get_sig_algo(): verification failed at marker "', id:upper(), '"\n') io.stderr:write( '=> Skipping cert #', certnum,' "', caname, '"\n') return false end local a,b,c,d,e,f,g,h=data:byte(1,8) if a~=48 then return algo_fail("a") end if b~=130 then return algo_fail("b") end if e~=48 then return algo_fail("e") end if f~=130 then return algo_fail("f") end if not g then return algo_fail("g") end if not h then return algo_fail("h") end local len=g*256+h -- actual length of certificate data len=len+8 -- plus the 8 bytes we already handled -- Signature algorithm comes immediately after the cert data, -- I have no idea what it all means :-) local i,j,k,l,m,n,o,p,q,r,s,t,u=data:byte(len+1,len+13) if i~=48 then return algo_fail("i") end if j>127 then return algo_fail("j") end if k~=6 then return algo_fail("k") end if l~=9 then return algo_fail("l") end if m~=42 then return algo_fail("m") end if n~=134 then return algo_fail("n") end if o~=72 then return algo_fail("o") end if p~=134 then return algo_fail("p") end if q~=247 then return algo_fail("q") end if r~=13 then return algo_fail("r") end if s~=1 then return algo_fail("s") end if t~=1 then return algo_fail("t") end -- byte 'u' should contain the algo id (md2=2; md5=4; sha1=5; etc.) -- not sure what else is supported, but x509_get_sig_alg() in the -- polarssl sources has a test that looks something like this: if u then if u>=2 and u<=5 then return true end if u>=11 and u<=14 then return true end end return algo_fail("u") end if make_backup then local src=io.open(crt,"r") if src then local backfile, err=nil for i=1,1024 do -- 1024 backups is all you get! backfile=crt.."."..i -- Lua doesn't have any built-in file tests, so trying to read -- a file is as close as we can get to confirming its existence. -- This could clobber an existing backup in the weird case that -- the backup has write permission, but not read permission. local trg=io.open(backfile, "r") if trg then -- file exists, skip this name trg:close() else trg=io.open(backfile, "w") if trg then trg:write(src:read("*a")) if trg:close() then io.stderr:write("Backup: ", crt, " => ", backfile, "\n") break end end end if i==1024 then io.stderr:write("FATAL: Failed to create backup: ", backfile, "\n") os.exit(1) end end src:close() end end -- Lua has no built-in network client, so try curl first, then wget. if do_download then io.stderr:write("Attempting download with curl, please wait...\n") local curl_rv=os.execute("curl --fail -Ss "..url..' -o '..certdata_txt) if curl_rv~=0 then io.stderr:write("Download with curl failed, error #",curl_rv,"\n"); io.stderr:write("Attempting download with wget, please wait...\n") local tempfile=certdata_txt..".MK-CA-BUNDLE-LUA-"..os.date("%s")..".TMP" local wget_rv=os.execute("wget -nv "..url..' -O '..tempfile) if wget_rv == 0 then local ok,err=os.rename(tempfile,certdata_txt) if not ok then io.stderr:write("Failed to rename downloaded file",err,"\n"); os.exit(1) end else io.stderr:write("Download with wget failed, error #",wget_rv,"\n"); io.stderr:write("Failed to download "..certdata_txt.."\n") os.remove(tempfile) os.exit(1) end end end function base64(s) -- by Diego Nehab, see license notice above -- local t64 = { -- Conversion table [ 0] = 'A', [ 1] = 'B', [ 2] = 'C', [ 3] = 'D', [ 4] = 'E', [ 5] = 'F', [ 6] = 'G', [ 7] = 'H', [ 8] = 'I', [ 9] = 'J', [10] = 'K', [11] = 'L', [12] = 'M', [13] = 'N', [14] = 'O', [15] = 'P', [16] = 'Q', [17] = 'R', [18] = 'S', [19] = 'T', [20] = 'U', [21] = 'V', [22] = 'W', [23] = 'X', [24] = 'Y', [25] = 'Z', [26] = 'a', [27] = 'b', [28] = 'c', [29] = 'd', [30] = 'e', [31] = 'f', [32] = 'g', [33] = 'h', [34] = 'i', [35] = 'j', [36] = 'k', [37] = 'l', [38] = 'm', [39] = 'n', [40] = 'o', [41] = 'p', [42] = 'q', [43] = 'r', [44] = 's', [45] = 't', [46] = 'u', [47] = 'v', [48] = 'w', [49] = 'x', [50] = 'y', [51] = 'z', [52] = '0', [53] = '1', [54] = '2', [55] = '3', [56] = '4', [57] = '5', [58] = '6', [59] = '7', [60] = '8', [61] = '9', [62] = '+', [63] = '/', } local t2f = function(a,b,c) -- Convert a 3-byte sequence into 4-char base64 local s = string.byte(a)*65536 + string.byte(b)*256 + string.byte(c) local ca, cb, cc, cd cd = math.mod(s, 64) s = (s - cd) / 64 cc = math.mod(s, 64) s = (s - cc) / 64 cb = math.mod(s, 64) ca = (s - cb) / 64 return t64[ca] .. t64[cb] .. t64[cc] .. t64[cd] end local to64pad = function(s) -- Create 4-char base64 of incomplete last block local a, b, ca, cb, cc _,_, a, b = string.find(s, "(.?)(.?)") if b == "" then s = string.byte(a)*16 cb = math.mod(s, 64) ca = (s - cb)/64 return t64[ca] .. t64[cb] .. "==" end s = string.byte(a)*1024 + string.byte(b)*4 cc = math.mod(s, 64) s = (s - cc) / 64 cb = math.mod(s, 64) ca = (s - cb)/64 return t64[ca] .. t64[cb] .. t64[cc] .. "=" end local l = #s local m = math.mod(l, 3) l = l - m if l > 0 then whole = string.gsub(string.sub(s, 1, l), "(.)(.)(.)", t2f) else whole = "" end if m > 0 then pad = to64pad(string.sub(s, l+1)) else pad = "" end return whole .. pad end -- base64 local infile, outfile, ok, err infile, err = io.open(certdata_txt,"r") if not infile then io.stderr:write("Failed to open file:",(err or "unknown error"),"\n") os.exit(1) end outfile, err = io.open(crt,"w") if not outfile then io.stderr:write("Failed to open file:",(err or "unknown error"),"\n") os.exit(1) end if use_comments then outfile:write( [[ ## ## ]]..crt..[[ -- Bundle of CA Root Certificates ## ## Converted at: ]] ..os.date('!%c').. [[ UTC ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates ## file (certdata.txt). This file can be found in the mozilla source tree: ## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt' ## ## It contains the certificates in PEM format and therefore ## can be directly used with curl / libcurl / php_curl, or with ## an Apache+mod_ssl webserver for SSL client authentication. ## Just configure this file as the SSLCACertificateFile. ## ]] ) end local in_license_block = false local caname = nil local data = nil local numcerts = 0 for line in infile:lines() do if use_comments then if line:find('***** BEGIN LICENSE BLOCK *****',1,true) then in_license_block=true elseif line:find('***** END LICENSE BLOCK *****',1,true) then outfile:write(line,"\n") in_license_block=false end end if in_license_block then outfile:write(line,"\n") elseif line:find("^CKA_LABEL%s+[A-Z0-9]+%s+") then caname=line:gsub('^CKA_LABEL%s+[A-Z0-9]+%s+"(.*)"',"%1") elseif line:find('^CKA_VALUE MULTILINE_OCTAL') then data="" elseif data then if line:find('^END') then if verbose then local pipe,err=io.popen('openssl x509 -md5 -fingerprint -text -inform DER', "w") if (pipe) then pipe:write(data) else io.stderr:write("Can't display cert info: ", err or "unknown error", "\n") end end if check_sig_algo(caname, numcerts+1, data) then if use_comments then outfile:write("\n",caname,"\n") outfile:write(string.rep('=',#caname),"\n") end data=base64(data) data=data:gsub('('..string.rep('.',wrap_margin)..')',"%1\n") outfile:write('-----BEGIN CERTIFICATE-----\n') outfile:write(data, data:find("\n$") and "" or "\n") outfile:write('-----END CERTIFICATE-----\n') numcerts=numcerts+1 if maxcerts>0 and numcerts>=maxcerts then infile:close() break end end data=nil else data=data..line:gsub('\\([0-7][0-7][0-7])', function(a) return string.char(tonumber(a,8)) end ) end elseif use_comments and line:find("^CVS_ID%s+") then outfile:write((line:gsub('^CVS_ID%s+"(.*)"',"# %1\n"))) end end ok,err=outfile:close() if not ok then io.stderr:write("Failed to save file:",(err or "unknown error"),"\n") os.exit(1) end if delete_certdata then os.remove(certdata_txt) end io.stderr:write("Done (",numcerts," CA certs processed).\n")