123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555 |
- /*
- * OpenConnect (SSL + DTLS) VPN client
- *
- * Copyright © 2008-2015 Intel Corporation.
- *
- * Author: David Woodhouse <dwmw2@infradead.org>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- */
- #include <config.h>
- #include "openconnect-internal.h"
- #include "gnutls.h"
- #include <gnutls/gnutls.h>
- #include <gnutls/x509.h>
- #include <gnutls/crypto.h>
- #include <gnutls/pkcs12.h>
- #include <gnutls/abstract.h>
- #ifdef HAVE_P11KIT
- #include <p11-kit/p11-kit.h>
- #include <p11-kit/pkcs11.h>
- #include <p11-kit/pin.h>
- #endif
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <string.h>
- #include <ctype.h>
- #include <stdio.h>
- #include <errno.h>
- #include <stdarg.h>
- #include <stdlib.h>
- #if defined(HAVE_P11KIT) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- static int gnutls_pin_callback(void *priv, int attempt, const char *uri,
- const char *token_label, unsigned int flags,
- char *pin, size_t pin_max);
- #endif /* HAVE_P11KIT || HAVE_GNUTLS_SYSTEM_KEYS */
- /* GnuTLS 2.x lacked this. But GNUTLS_E_UNEXPECTED_PACKET_LENGTH basically
- * does the same thing.
- * https://lists.infradead.org/pipermail/openconnect-devel/2014-March/001726.html
- */
- #ifndef GNUTLS_E_PREMATURE_TERMINATION
- #define GNUTLS_E_PREMATURE_TERMINATION GNUTLS_E_UNEXPECTED_PACKET_LENGTH
- #endif
- /* GnuTLS 3.5.0 added this flag to send a client cert, even if its issuer is
- * mismatched to the list of issuers requested by the server. OpenSSL does
- * this by default.
- * https://github.com/curl/curl/issues/1411
- */
- #ifndef GNUTLS_FORCE_CLIENT_CERT
- #define GNUTLS_FORCE_CLIENT_CERT 0
- #endif
- static char tls_library_version[32] = "";
- const char *openconnect_get_tls_library_version(void)
- {
- if (!*tls_library_version) {
- snprintf(tls_library_version, sizeof(tls_library_version), "GnuTLS %s",
- gnutls_check_version(NULL));
- }
- return tls_library_version;
- }
- int can_enable_insecure_crypto(void)
- {
- /* XX: As of GnuTLS 3.6.13, no released version has (yet) removed 3DES/RC4 from default builds,
- * but like OpenSSL (removed in 1.1.0) it may happen. */
- if (gnutls_cipher_get_id("3DES-CBC") == GNUTLS_CIPHER_UNKNOWN ||
- gnutls_cipher_get_id("ARCFOUR-128") == GNUTLS_CIPHER_UNKNOWN)
- return -ENOENT;
- return 0;
- }
- /* Helper functions for reading/writing lines over TLS/DTLS. */
- static int _openconnect_gnutls_write(gnutls_session_t ses, int fd, struct openconnect_info *vpninfo, char *buf, size_t len)
- {
- size_t orig_len = len;
- while (len) {
- int done = gnutls_record_send(ses, buf, len);
- if (done > 0)
- len -= done;
- else if (done == GNUTLS_E_AGAIN || done == GNUTLS_E_INTERRUPTED) {
- /* Wait for something to happen on the socket, or on cmd_fd */
- fd_set wr_set, rd_set;
- int maxfd = fd;
- FD_ZERO(&wr_set);
- FD_ZERO(&rd_set);
- if (gnutls_record_get_direction(ses))
- FD_SET(fd, &wr_set);
- else
- FD_SET(fd, &rd_set);
- cmd_fd_set(vpninfo, &rd_set, &maxfd);
- if (select(maxfd + 1, &rd_set, &wr_set, NULL, NULL) < 0 &&
- errno != EINTR) {
- vpn_perror(vpninfo, _("Failed select() for TLS"));
- return -EIO;
- }
- if (is_cancel_pending(vpninfo, &rd_set)) {
- vpn_progress(vpninfo, PRG_ERR, _("TLS/DTLS write cancelled\n"));
- return -EINTR;
- }
- } else {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to write to TLS/DTLS socket: %s\n"),
- gnutls_strerror(done));
- return -EIO;
- }
- }
- return orig_len;
- }
- static int openconnect_gnutls_write(struct openconnect_info *vpninfo, char *buf, size_t len)
- {
- return _openconnect_gnutls_write(vpninfo->https_sess, vpninfo->ssl_fd, vpninfo, buf, len);
- }
- int openconnect_dtls_write(struct openconnect_info *vpninfo, void *buf, size_t len)
- {
- return _openconnect_gnutls_write(vpninfo->dtls_ssl, vpninfo->dtls_fd, vpninfo, buf, len);
- }
- static int _openconnect_gnutls_read(gnutls_session_t ses, int fd, struct openconnect_info *vpninfo, char *buf, size_t len, unsigned ms)
- {
- int done, ret;
- struct timeval timeout, *tv = NULL;
- if (ms) {
- timeout.tv_sec = ms/1000;
- timeout.tv_usec = (ms%1000)*1000;
- tv = &timeout;
- }
- while ((done = gnutls_record_recv(ses, buf, len)) < 0) {
- if (done == GNUTLS_E_AGAIN || done == GNUTLS_E_INTERRUPTED) {
- /* Wait for something to happen on the socket, or on cmd_fd */
- fd_set wr_set, rd_set;
- int maxfd = fd;
- FD_ZERO(&wr_set);
- FD_ZERO(&rd_set);
- if (gnutls_record_get_direction(ses))
- FD_SET(fd, &wr_set);
- else
- FD_SET(fd, &rd_set);
- cmd_fd_set(vpninfo, &rd_set, &maxfd);
- ret = select(maxfd + 1, &rd_set, &wr_set, NULL, tv);
- if (ret < 0 && errno != EINTR) {
- vpn_perror(vpninfo, _("Failed select() for TLS/DTLS"));
- return -EIO;
- }
- if (is_cancel_pending(vpninfo, &rd_set)) {
- vpn_progress(vpninfo, PRG_ERR, _("TLS/DTLS read cancelled\n"));
- done = -EINTR;
- goto cleanup;
- }
- if (ret == 0) {
- done = -ETIMEDOUT;
- goto cleanup;
- }
- } else if (done == GNUTLS_E_PREMATURE_TERMINATION) {
- /* We've seen this with HTTP 1.0 responses followed by abrupt
- socket closure and no clean SSL shutdown.
- https://bugs.launchpad.net/bugs/1225276 */
- vpn_progress(vpninfo, PRG_DEBUG, _("TLS/DTLS socket closed uncleanly\n"));
- done = 0;
- goto cleanup;
- } else if (done == GNUTLS_E_REHANDSHAKE) {
- int ret = cstp_handshake(vpninfo, 0);
- if (ret) {
- done = ret;
- goto cleanup;
- }
- } else {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to read from TLS/DTLS socket: %s\n"),
- gnutls_strerror(done));
- if (done == GNUTLS_E_TIMEDOUT) {
- done = -ETIMEDOUT;
- goto cleanup;
- } else {
- done = -EIO;
- goto cleanup;
- }
- }
- }
- cleanup:
- return done;
- }
- static int openconnect_gnutls_read(struct openconnect_info *vpninfo, char *buf, size_t len)
- {
- return _openconnect_gnutls_read(vpninfo->https_sess, vpninfo->ssl_fd, vpninfo, buf, len, 0);
- }
- int openconnect_dtls_read(struct openconnect_info *vpninfo, void *buf, size_t len, unsigned ms)
- {
- return _openconnect_gnutls_read(vpninfo->dtls_ssl, vpninfo->dtls_fd, vpninfo, buf, len, ms);
- }
- static int openconnect_gnutls_gets(struct openconnect_info *vpninfo, char *buf, size_t len)
- {
- int i = 0;
- int ret;
- if (len < 2)
- return -EINVAL;
- while (1) {
- ret = gnutls_record_recv(vpninfo->https_sess, buf + i, 1);
- if (ret == 1) {
- if (buf[i] == '\n') {
- buf[i] = 0;
- if (i && buf[i-1] == '\r') {
- buf[i-1] = 0;
- i--;
- }
- return i;
- }
- i++;
- if (i >= len - 1) {
- buf[i] = 0;
- return i;
- }
- } else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) {
- /* Wait for something to happen on the socket, or on cmd_fd */
- fd_set rd_set, wr_set;
- int maxfd = vpninfo->ssl_fd;
- FD_ZERO(&rd_set);
- FD_ZERO(&wr_set);
- if (gnutls_record_get_direction(vpninfo->https_sess))
- FD_SET(vpninfo->ssl_fd, &wr_set);
- else
- FD_SET(vpninfo->ssl_fd, &rd_set);
- cmd_fd_set(vpninfo, &rd_set, &maxfd);
- if (select(maxfd + 1, &rd_set, &wr_set, NULL, NULL) < 0 &&
- errno != EINTR) {
- vpn_perror(vpninfo, _("Failed select() for TLS"));
- return -EIO;
- }
- if (is_cancel_pending(vpninfo, &rd_set)) {
- vpn_progress(vpninfo, PRG_ERR, _("TLS/DTLS read cancelled\n"));
- ret = -EINTR;
- break;
- }
- } else if (ret == GNUTLS_E_REHANDSHAKE) {
- ret = cstp_handshake(vpninfo, 0);
- if (ret)
- return ret;
- } else {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to read from TLS/DTLS socket: %s\n"),
- gnutls_strerror(ret));
- ret = -EIO;
- break;
- }
- }
- buf[i] = 0;
- return i ?: ret;
- }
- int ssl_nonblock_read(struct openconnect_info *vpninfo, int dtls, void *buf, int maxlen)
- {
- gnutls_session_t sess = dtls ? vpninfo->dtls_ssl : vpninfo->https_sess;
- int ret;
- if (!sess) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Attempted to read from non-existent %s session\n"),
- dtls ? "DTLS" : "TLS");
- return -1;
- }
- ret = gnutls_record_recv(sess, buf, maxlen);
- if (ret > 0)
- return ret;
- if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
- return 0;
- vpn_progress(vpninfo, PRG_ERR, _("Read error on %s session: %s\n"),
- dtls ? "DTLS" : "SSL", gnutls_strerror(ret));
- return -1;
- }
- int ssl_nonblock_write(struct openconnect_info *vpninfo, int dtls, void *buf, int buflen)
- {
- gnutls_session_t sess = dtls ? vpninfo->dtls_ssl : vpninfo->https_sess;
- int ret;
- if (!sess) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Attempted to write to non-existent %s session\n"),
- dtls ? "DTLS" : "TLS");
- return -1;
- }
- ret = gnutls_record_send(sess, buf, buflen);
- if (ret > 0)
- return ret;
- if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) {
- /*
- * Before 3.3.13, GnuTLS could return zero instead of one,
- * indicating that it was waiting for a read when in fact
- * it was waiting for a write. That caused us to block for
- * ever, waiting for the read that it said it wanted.
- *
- * So instead, just *assume* it actually wants a write.
- * Which is true most of the time, and on the rare occasion
- * that it *isn't* true, the failure mode will just be that
- * we keep waking up and calling GnuTLS again until the read
- * that it's waiting for does arrive.
- */
- if (GNUTLS_VERSION_NUMBER < 0x03030d ||
- gnutls_record_get_direction(sess)) {
- /* Waiting for the socket to become writable — it's
- probably stalled, and/or the buffers are full */
- if (dtls)
- monitor_write_fd(vpninfo, dtls);
- else
- monitor_write_fd(vpninfo, ssl);
- }
- return 0;
- }
- vpn_progress(vpninfo, PRG_ERR, _("Write error on %s session: %s\n"),
- dtls ? "DTLS" : "SSL", gnutls_strerror(ret));
- return -1;
- }
- static int check_certificate_expiry(struct openconnect_info *vpninfo, struct cert_info *certinfo,
- gnutls_x509_crt_t cert)
- {
- const char *reason = NULL;
- time_t expires = gnutls_x509_crt_get_expiration_time(cert);
- time_t now = time(NULL);
- if (expires == -1) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Could not extract expiration time of certificate\n"));
- return -EINVAL;
- }
- if (expires < now)
- reason = certinfo_string(certinfo, _("Client certificate has expired at"),
- _("Secondary client certificate has expired at"));
- else if (expires < now + vpninfo->cert_expire_warning)
- reason = certinfo_string(certinfo, _("Client certificate expires soon at"),
- _("Secondary client certificate expires soon at"));
- if (reason) {
- char buf[80];
- #ifdef _WIN32
- /*
- * Windows doesn't have gmtime_r but apparently its gmtime()
- * *is* thread-safe because it uses a per-thread static buffer.
- * cf. https://sourceforge.net/p/mingw/bugs/1625/
- *
- * We also explicitly say 'GMT' because %Z would give us the
- * Microsoft stupidity "GMT Standard Time". Which is not only
- * silly, but also ambiguous because Windows actually says that
- * even when it means British Summer Time (GMT+1). And having
- * used gmtime() we really *are* giving the time in GMT.
- */
- struct tm *tm = gmtime(&expires);
- strftime(buf, 80, "%a, %d %b %Y %H:%M:%S GMT", tm);
- #else
- struct tm tm;
- gmtime_r(&expires, &tm);
- strftime(buf, 80, "%a, %d %b %Y %T %Z", &tm);
- #endif
- vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", reason, buf);
- }
- return 0;
- }
- static int load_datum(struct openconnect_info *vpninfo,
- gnutls_datum_t *datum, const char *fname)
- {
- struct stat st;
- int fd;
- #ifdef ANDROID_KEYSTORE
- if (!strncmp(fname, "keystore:", 9)) {
- int len;
- const char *p = fname + 9;
- /* Skip first two slashes if the user has given it as
- keystore://foo ... */
- if (*p == '/')
- p++;
- if (*p == '/')
- p++;
- len = keystore_fetch(p, &datum->data);
- if (len <= 0) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to load item '%s' from keystore: %s\n"),
- p, keystore_strerror(len));
- return -EINVAL;
- }
- datum->size = len;
- return 0;
- }
- #endif /* ANDROID_KEYSTORE */
- fd = openconnect_open_utf8(vpninfo, fname, O_RDONLY|O_CLOEXEC|O_BINARY);
- if (fd == -1) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to open key/certificate file %s: %s\n"),
- fname, strerror(errno));
- return -ENOENT;
- }
- if (fstat(fd, &st)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to stat key/certificate file %s: %s\n"),
- fname, strerror(errno));
- close(fd);
- return -EIO;
- }
- datum->size = st.st_size;
- datum->data = gnutls_malloc(st.st_size + 1);
- if (!datum->data) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to allocate certificate buffer\n"));
- close(fd);
- return -ENOMEM;
- }
- errno = EAGAIN;
- if (read(fd, datum->data, datum->size) != datum->size) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to read certificate into memory: %s\n"),
- strerror(errno));
- close(fd);
- gnutls_free(datum->data);
- return -EIO;
- }
- datum->data[st.st_size] = 0;
- close(fd);
- return 0;
- }
- /* A non-zero, non-error return to make load_certificate() continue and
- interpreting the file as other types */
- #define NOT_PKCS12 1
- static int load_pkcs12_certificate(struct openconnect_info *vpninfo,
- struct cert_info *certinfo,
- gnutls_datum_t *datum,
- gnutls_x509_privkey_t *key,
- gnutls_x509_crt_t **chain,
- unsigned int *chain_len,
- gnutls_x509_crt_t **extra_certs,
- unsigned int *extra_certs_len,
- gnutls_x509_crl_t *crl)
- {
- gnutls_pkcs12_t p12;
- char *pass;
- int err;
- err = gnutls_pkcs12_init(&p12);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to setup PKCS#12 data structure: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- err = gnutls_pkcs12_import(p12, datum, GNUTLS_X509_FMT_DER, 0);
- if (err) {
- gnutls_pkcs12_deinit(p12);
- return NOT_PKCS12;
- }
- pass = certinfo->password;
- while ((err = gnutls_pkcs12_verify_mac(p12, pass)) == GNUTLS_E_MAC_VERIFY_FAILED) {
- if (!pass) {
- /* OpenSSL's PKCS12_parse() code will try both NULL and "" automatically,
- * but GnuTLS requires two separate attempts. */
- err = gnutls_pkcs12_verify_mac(p12, "");
- if (!err) {
- pass = strdup("");
- break;
- }
- } else
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to decrypt PKCS#12 certificate file\n"));
- free_pass(&pass);
- certinfo->password = NULL;
- err = request_passphrase(vpninfo,
- certinfo_string(certinfo, "openconnect_pkcs12",
- "openconnect_secondary_pkcs12"),
- &pass,
- certinfo_string(certinfo, _("Enter PKCS#12 pass phrase:"),
- _("Enter secondary PKCS#12 pass phrase:")));
- if (err) {
- gnutls_pkcs12_deinit(p12);
- return -EINVAL;
- }
- }
- /* If it wasn't GNUTLS_E_MAC_VERIFY_FAILED, then the problem wasn't just a
- bad password. Give up. */
- if (err) {
- int level = PRG_ERR;
- int ret = -EINVAL;
- gnutls_pkcs12_deinit(p12);
- /* If the first attempt, and we didn't know for sure it was PKCS#12
- anyway, bail out and try loading it as something different. */
- if (pass == certinfo->password) {
- /* Make it non-fatal... */
- level = PRG_DEBUG;
- ret = NOT_PKCS12;
- }
- vpn_progress(vpninfo, level,
- _("Failed to process PKCS#12 file: %s\n"),
- gnutls_strerror(err));
- return ret;
- }
- err = gnutls_pkcs12_simple_parse(p12, pass, key, chain, chain_len,
- extra_certs, extra_certs_len, crl, 0);
- free_pass(&pass);
- certinfo->password = NULL;
- gnutls_pkcs12_deinit(p12);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- certinfo_string(certinfo, _("Failed to load PKCS#12 certificate: %s\n"),
- _("Failed to load secondary PKCS#12 certificate: %s\n")),
- gnutls_strerror(err));
- return -EINVAL;
- }
- return 0;
- }
- static int count_x509_certificates(gnutls_datum_t *datum)
- {
- int count = 0;
- char *p = (char *)datum->data;
- while (p) {
- p = strstr(p, "-----BEGIN ");
- if (!p)
- break;
- p += 11;
- if (!strncmp(p, "CERTIFICATE", 11) ||
- !strncmp(p, "X509 CERTIFICATE", 16))
- count++;
- }
- return count;
- }
- static int get_cert_name(gnutls_x509_crt_t cert, char *name, size_t namelen)
- {
- /* When the name buffer is not big enough, gnutls_x509_crt_get_dn*() will
- * update the length argument to the required size, and return
- * GNUTLS_E_SHORT_MEMORY_BUFFER. We need to avoid clobbering the original
- * length variable. */
- size_t nl = namelen;
- if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME,
- 0, 0, name, &nl)) {
- nl = namelen;
- if (gnutls_x509_crt_get_dn(cert, name, &nl)) {
- name[namelen-1] = 0;
- snprintf(name, namelen-1, "<unknown>");
- return -EINVAL;
- }
- }
- return 0;
- }
- #if defined(HAVE_P11KIT) || defined(HAVE_TROUSERS) || defined(HAVE_TSS2) || defined (HAVE_GNUTLS_SYSTEM_KEYS)
- static int verify_signed_data(gnutls_pubkey_t pubkey, gnutls_privkey_t privkey,
- gnutls_digest_algorithm_t dig,
- const gnutls_datum_t *data, const gnutls_datum_t *sig)
- {
- gnutls_sign_algorithm_t algo;
- unsigned flags = 0;
- #ifdef GNUTLS_VERIFY_ALLOW_BROKEN
- flags |= GNUTLS_VERIFY_ALLOW_BROKEN;
- #endif
- algo = gnutls_pk_to_sign(gnutls_privkey_get_pk_algorithm(privkey, NULL),
- dig);
- return gnutls_pubkey_verify_data2(pubkey, algo, flags, data, sig);
- }
- #endif /* (P11KIT || TROUSERS || TSS2 || SYSTEM_KEYS) */
- static int openssl_hash_password(struct openconnect_info *vpninfo, char *pass,
- gnutls_datum_t *key, gnutls_datum_t *salt)
- {
- unsigned char md5[16];
- gnutls_hash_hd_t hash;
- int count = 0;
- int err;
- while (count < key->size) {
- err = gnutls_hash_init(&hash, GNUTLS_DIG_MD5);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Could not initialise MD5 hash: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- if (count) {
- err = gnutls_hash(hash, md5, sizeof(md5));
- if (err) {
- hash_err:
- gnutls_hash_deinit(hash, NULL);
- vpn_progress(vpninfo, PRG_ERR,
- _("MD5 hash error: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- }
- if (pass) {
- err = gnutls_hash(hash, pass, strlen(pass));
- if (err)
- goto hash_err;
- }
- /* We only use the first 8 bytes of the salt for this */
- err = gnutls_hash(hash, salt->data, 8);
- if (err)
- goto hash_err;
- gnutls_hash_deinit(hash, md5);
- if (key->size - count <= sizeof(md5)) {
- memcpy(&key->data[count], md5, key->size - count);
- break;
- }
- memcpy(&key->data[count], md5, sizeof(md5));
- count += sizeof(md5);
- }
- return 0;
- }
- static int import_openssl_pem(struct openconnect_info *vpninfo, struct cert_info *certinfo,
- gnutls_x509_privkey_t key,
- char type, char *pem_header, size_t pem_size)
- {
- gnutls_cipher_hd_t handle;
- gnutls_cipher_algorithm_t cipher;
- gnutls_datum_t constructed_pem;
- gnutls_datum_t b64_data;
- gnutls_datum_t salt, enc_key;
- unsigned char *key_data;
- const char *begin;
- char *pass, *p;
- char *pem_start = pem_header;
- int ret, err, i;
- if (type == 'E')
- begin = "EC PRIVATE KEY";
- else if (type == 'R')
- begin = "RSA PRIVATE KEY";
- else if (type == 'D')
- begin = "DSA PRIVATE KEY";
- else
- return -EINVAL;
- while (*pem_header == '\r' || *pem_header == '\n')
- pem_header++;
- if (strncmp(pem_header, "DEK-Info: ", 10)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Missing DEK-Info: header from OpenSSL encrypted key\n"));
- return -EIO;
- }
- pem_header += 10;
- p = strchr(pem_header, ',');
- if (!p) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Cannot determine PEM encryption type\n"));
- return -EINVAL;
- }
- *p = 0;
- cipher = gnutls_cipher_get_id(pem_header);
- /* GnuTLS calls this '3DES-CBC' but all other names match */
- if (cipher == GNUTLS_CIPHER_UNKNOWN &&
- !strcmp(pem_header, "DES-EDE3-CBC"))
- cipher = GNUTLS_CIPHER_3DES_CBC;
- if (cipher == GNUTLS_CIPHER_UNKNOWN) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Unsupported PEM encryption type: %s\n"),
- pem_header);
- return -EINVAL;
- }
- pem_header = p + 1;
- /* No supported algorithms have an IV larger than this, and dynamically
- allocating it would be painful. */
- salt.size = 64;
- salt.data = malloc(salt.size);
- if (!salt.data)
- return -ENOMEM;
- for (i = 0; i < salt.size * 2; i++) {
- unsigned char x;
- char *c = &pem_header[i];
- if (*c >= '0' && *c <= '9')
- x = (*c) - '0';
- else if (*c >= 'A' && *c <= 'F')
- x = (*c) - 'A' + 10;
- else if ((*c == '\r' || *c == '\n') && i >= 16 && !(i % 16)) {
- salt.size = i / 2;
- break;
- } else {
- vpn_progress(vpninfo, PRG_ERR,
- _("Invalid salt in encrypted PEM file\n"));
- ret = -EINVAL;
- goto out_salt;
- }
- if (i & 1)
- salt.data[i/2] |= x;
- else
- salt.data[i/2] = x << 4;
- }
- pem_header += salt.size * 2;
- if (*pem_header != '\r' && *pem_header != '\n') {
- vpn_progress(vpninfo, PRG_ERR,
- _("Invalid salt in encrypted PEM file\n"));
- ret = -EINVAL;
- goto out_salt;
- }
- while (*pem_header == '\n' || *pem_header == '\r')
- pem_header++;
- /* pem_header should now point to the start of the base64 content.
- Put a -----BEGIN banner in place before it, so that we can use
- gnutls_pem_base64_decode_alloc(). The banner has to match the
- -----END banner, so make sure we get it right... */
- pem_header -= 6;
- memcpy(pem_header, "-----\n", 6);
- pem_header -= strlen(begin);
- memcpy(pem_header, begin, strlen(begin));
- pem_header -= 11;
- memcpy(pem_header, "-----BEGIN ", 11);
- constructed_pem.data = (void *)pem_header;
- constructed_pem.size = pem_size - (pem_header - pem_start);
- err = gnutls_pem_base64_decode_alloc(begin, &constructed_pem, &b64_data);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error base64-decoding encrypted PEM file: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out_salt;
- }
- if (b64_data.size < 16) {
- /* Just to be sure our parsing is OK */
- vpn_progress(vpninfo, PRG_ERR,
- _("Encrypted PEM file too short\n"));
- ret = -EINVAL;
- goto out_b64;
- }
- ret = -ENOMEM;
- enc_key.size = gnutls_cipher_get_key_size(cipher);
- enc_key.data = malloc(enc_key.size);
- if (!enc_key.data)
- goto out_b64;
- key_data = malloc(b64_data.size);
- if (!key_data)
- goto out_enc_key;
- pass = certinfo->password;
- certinfo->password = NULL;
- while (1) {
- memcpy(key_data, b64_data.data, b64_data.size);
- ret = openssl_hash_password(vpninfo, pass, &enc_key, &salt);
- if (ret)
- goto out;
- err = gnutls_cipher_init(&handle, cipher, &enc_key, &salt);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to initialise cipher for decrypting PEM file: %s\n"),
- gnutls_strerror(err));
- gnutls_cipher_deinit(handle);
- ret = -EIO;
- goto out;
- }
- err = gnutls_cipher_decrypt(handle, key_data, b64_data.size);
- gnutls_cipher_deinit(handle);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to decrypt PEM key: %s\n"),
- gnutls_strerror(err));
- ret = -EIO;
- goto out;
- }
- /* We have to strip any padding for GnuTLS to accept it.
- So a bit more ASN.1 parsing for us.
- FIXME: Consolidate with similar code in gnutls_tpm.c */
- if (key_data[0] == 0x30) {
- gnutls_datum_t key_datum;
- int blocksize = gnutls_cipher_get_block_size(cipher);
- int keylen = key_data[1];
- int ofs = 2;
- if (keylen & 0x80) {
- int lenlen = keylen & 0x7f;
- keylen = 0;
- if (lenlen > 3)
- goto fail;
- while (lenlen) {
- keylen <<= 8;
- keylen |= key_data[ofs++];
- lenlen--;
- }
- }
- keylen += ofs;
- /* If there appears to be more or less padding than required, fail */
- if (b64_data.size - keylen > blocksize || b64_data.size < keylen + 1)
- goto fail;
- /* If the padding bytes aren't all equal to the amount of padding, fail */
- ofs = keylen;
- while (ofs < b64_data.size) {
- if (key_data[ofs] != b64_data.size - keylen)
- goto fail;
- ofs++;
- }
- key_datum.data = key_data;
- key_datum.size = keylen;
- err = gnutls_x509_privkey_import(key, &key_datum, GNUTLS_X509_FMT_DER);
- if (!err) {
- ret = 0;
- goto out;
- }
- }
- fail:
- if (pass) {
- vpn_progress(vpninfo, PRG_ERR, _("Decrypting PEM key failed\n"));
- free_pass(&pass);
- }
- err = request_passphrase(vpninfo,
- certinfo_string(certinfo, "openconnect_pem",
- "openconnect_secondary_pem"),
- &pass,
- certinfo_string(certinfo, _("Enter PEM pass phrase:"),
- _("Enter secondary PEM pass phrase:")));
- if (err) {
- ret = -EINVAL;
- goto out;
- }
- }
- out:
- free(key_data);
- free_pass(&pass);
- out_enc_key:
- free(enc_key.data);
- out_b64:
- free(b64_data.data);
- out_salt:
- free(salt.data);
- return ret;
- }
- static void fill_token_info(char *buf, size_t s, unsigned char *dst, size_t dstlen)
- {
- if (s && !gtls_ver(3,6,0))
- s--;
- if (s > dstlen)
- s = dstlen;
- memcpy(dst, buf, s);
- if (s < dstlen)
- memset(dst + s, ' ', dstlen - s);
- }
- struct gtls_cert_info {
- gnutls_x509_crl_t crl;
- gnutls_privkey_t pkey;
- gnutls_x509_crt_t *certs;
- unsigned int nr_certs;
- };
- void unload_certificate(struct cert_info *certinfo, int final)
- {
- if (!certinfo)
- return;
- if (certinfo->priv_info) {
- struct gtls_cert_info *gci = certinfo->priv_info;
- certinfo->priv_info = NULL;
- gnutls_x509_crl_deinit(gci->crl);
- gnutls_privkey_deinit(gci->pkey);
- for (size_t i = 0, end = gci->nr_certs; i < end; i++)
- gnutls_x509_crt_deinit(gci->certs[i]);
- gnutls_free(gci->certs);
- free(gci);
- }
- if (final) {
- #if defined(OPENCONNECT_GNUTLS) && defined(HAVE_TROUSERS)
- release_tpm1_ctx(certinfo->vpninfo, certinfo);
- #endif
- #if defined(OPENCONNECT_GNUTLS) && defined(HAVE_TSS2)
- release_tpm2_ctx(certinfo->vpninfo, certinfo);
- #endif
- }
- }
- static int import_cert(gnutls_x509_crt_t *cert, const gnutls_datum_t *der)
- {
- gnutls_x509_crt_t crt = NULL;
- int ret;
- if (!cert)
- return GNUTLS_E_INVALID_REQUEST;
- ret = gnutls_x509_crt_init(&crt);
- if (ret < 0)
- goto done;
- ret = gnutls_x509_crt_import(crt, der, GNUTLS_X509_FMT_DER);
- if (ret < 0) {
- gnutls_x509_crt_deinit(crt);
- crt = NULL;
- }
- done:
- *cert = crt;
- return ret;
- }
- static int copy_cert(gnutls_x509_crt_t *cert_copy, gnutls_x509_crt_t cert)
- {
- gnutls_datum_t data = { NULL, 0 };
- gnutls_x509_crt_t copy = NULL;
- int ret;
- if (!cert_copy)
- return GNUTLS_E_INVALID_REQUEST;
- ret = gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &data);
- if (ret < 0)
- goto done;
- ret = import_cert(©, &data);
- gnutls_free(data.data);
- done:
- *cert_copy = copy;
- return ret;
- }
- static int check_multicert_compat(struct openconnect_info *vpninfo, struct cert_info *certinfo);
- int load_certificate(struct openconnect_info *vpninfo, struct cert_info *certinfo, int flags)
- {
- gnutls_datum_t fdata;
- #if defined(HAVE_P11KIT) || defined(HAVE_TROUSERS) || defined(HAVE_TSS2) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- gnutls_datum_t pkey_sig = {NULL, 0};
- void *dummy_hash_data = &load_certificate;
- #endif
- #if defined(HAVE_P11KIT) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- char *cert_url = (char *)certinfo->cert;
- #endif
- #ifdef HAVE_P11KIT
- char *key_url = (char *)certinfo->key;
- gnutls_pkcs11_privkey_t p11key = NULL;
- #endif
- char *pem_header;
- gnutls_x509_crt_t last_cert, cert = NULL;
- gnutls_x509_crt_t *extra_certs = NULL;
- unsigned int nr_extra_certs = 0;
- int err; /* GnuTLS error */
- int ret;
- int i;
- int cert_is_p11 = 0, key_is_p11 = 0;
- int cert_is_sys = 0, key_is_sys = 0;
- unsigned char key_id[20];
- size_t key_id_size = sizeof(key_id);
- char name[80];
- gnutls_x509_privkey_t x509key = NULL;
- struct gtls_cert_info *gci = NULL;
- certinfo->vpninfo = vpninfo;
- fdata.data = NULL;
- key_is_p11 = !strncmp(certinfo->key, "pkcs11:", 7);
- cert_is_p11 = !strncmp(certinfo->cert, "pkcs11:", 7);
- /* GnuTLS returns true for pkcs11:, tpmkey:, system:, and custom URLs. */
- key_is_sys = !key_is_p11 && gnutls_url_is_supported(certinfo->key);
- cert_is_sys = !cert_is_p11 && gnutls_url_is_supported(certinfo->cert);
- #ifndef HAVE_GNUTLS_SYSTEM_KEYS
- if (key_is_sys || cert_is_sys) {
- vpn_progress(vpninfo, PRG_ERR,
- _("This binary built without system key support\n"));
- return -EINVAL;
- }
- #endif
- #ifndef HAVE_P11KIT
- if (key_is_p11 || cert_is_p11) {
- vpn_progress(vpninfo, PRG_ERR,
- _("This binary built without PKCS#11 support\n"));
- return -EINVAL;
- }
- #else
- /* Install PIN handler if either certificate or key are coming from PKCS#11 */
- if (key_is_p11 || cert_is_p11) {
- CK_OBJECT_CLASS class;
- CK_ATTRIBUTE attr;
- P11KitUri *uri;
- uri = p11_kit_uri_new();
- attr.type = CKA_CLASS;
- attr.pValue = &class;
- attr.ulValueLen = sizeof(class);
- /* Add appropriate pin-source and object-type attributes to
- both certificate and key URLs, unless they already exist. */
- if (cert_is_p11 &&
- !p11_kit_uri_parse(cert_url, P11_KIT_URI_FOR_ANY, uri)) {
- if (!p11_kit_uri_get_attribute(uri, CKA_CLASS)) {
- class = CKO_CERTIFICATE;
- p11_kit_uri_set_attribute(uri, &attr);
- }
- p11_kit_uri_format(uri, P11_KIT_URI_FOR_ANY, &cert_url);
- }
- if (key_is_p11 &&
- !p11_kit_uri_parse(key_url, P11_KIT_URI_FOR_ANY, uri)) {
- if (certinfo->key == certinfo->cert ||
- !p11_kit_uri_get_attribute(uri, CKA_CLASS)) {
- class = CKO_PRIVATE_KEY;
- p11_kit_uri_set_attribute(uri, &attr);
- }
- p11_kit_uri_format(uri, P11_KIT_URI_FOR_ANY, &key_url);
- }
- p11_kit_uri_free(uri);
- }
- #endif /* HAVE_PKCS11 */
- certinfo->priv_info = gci = calloc(1, sizeof(*gci));
- if (!gci) {
- ret = -ENOMEM;
- goto out;
- }
- #if defined (HAVE_P11KIT) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- /* Load certificate(s) first... */
- if (cert_is_p11 || cert_is_sys) {
- vpn_progress(vpninfo, PRG_DEBUG,
- cert_is_p11 ? _("Using PKCS#11 certificate %s\n") :
- _("Using system certificate %s\n"), cert_url);
- err = gnutls_x509_crt_init(&cert);
- if (err) {
- ret = -ENOMEM;
- goto out;
- }
- gnutls_x509_crt_set_pin_function(cert, gnutls_pin_callback, certinfo);
- /* Yes, even for *system* URLs the only API GnuTLS offers us is
- ...import_pkcs11_url(). */
- err = gnutls_x509_crt_import_pkcs11_url(cert, cert_url, 0);
- if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
- err = gnutls_x509_crt_import_pkcs11_url(cert, cert_url,
- GNUTLS_PKCS11_OBJ_FLAG_LOGIN);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- cert_is_p11 ? _("Error loading certificate from PKCS#11: %s\n") :
- _("Error loading system certificate: %s\n"),
- gnutls_strerror(err));
- ret = -EIO;
- goto out;
- }
- goto got_certs;
- }
- #endif /* HAVE_P11KIT || HAVE_GNUTLS_SYSTEM_KEYS */
- /* OK, not a PKCS#11 certificate so it must be coming from a file... */
- vpn_progress(vpninfo, PRG_DEBUG,
- certinfo_string(certinfo, _("Using certificate file %s\n"),
- _("Using secondary certificate file %s\n")),
- certinfo->cert);
- /* Load file contents */
- ret = load_datum(vpninfo, &fdata, certinfo->cert);
- if (ret)
- return ret;
- /* Is it PKCS#12? */
- if (!key_is_p11) {
- /* PKCS#12 should actually contain certificates *and* private key */
- ret = load_pkcs12_certificate(vpninfo, certinfo, &fdata, &x509key,
- &gci->certs, &gci->nr_certs,
- &extra_certs, &nr_extra_certs,
- &gci->crl);
- if (ret < 0)
- goto out;
- else if (!ret) {
- if (gci->nr_certs) {
- cert = gci->certs[0];
- goto got_key;
- }
- vpn_progress(vpninfo, PRG_ERR,
- _("PKCS#11 file contained no certificate\n"));
- ret = -EINVAL;
- goto out;
- }
- /* It returned NOT_PKCS12.
- Fall through to try PEM formats. */
- }
- /* We need to know how many there are in *advance*; it won't just allocate
- the array for us :( */
- nr_extra_certs = count_x509_certificates(&fdata);
- if (!nr_extra_certs)
- nr_extra_certs = 1; /* wtf? Oh well, we'll fail later... */
- extra_certs = gnutls_calloc(nr_extra_certs, sizeof(cert));
- if (!extra_certs) {
- nr_extra_certs = 0;
- ret = -ENOMEM;
- goto out;
- }
- err = gnutls_x509_crt_list_import(extra_certs, &nr_extra_certs, &fdata,
- GNUTLS_X509_FMT_PEM, 0);
- if (err <= 0) {
- const char *reason;
- if (!err || err == GNUTLS_E_NO_CERTIFICATE_FOUND)
- reason = _("No certificate found in file");
- else
- reason = gnutls_strerror(err);
- vpn_progress(vpninfo, PRG_ERR,
- certinfo_string(certinfo, _("Loading certificate failed: %s\n"),
- _("Loading secondary certificate failed: %s\n")),
- reason);
- nr_extra_certs = 0;
- ret = -EINVAL;
- goto out;
- }
- nr_extra_certs = err;
- goto got_certs;
- got_certs:
- /* Now we have either a single certificate in 'cert', or an array of
- them in extra_certs[]. Next we look for the private key ... */
- #ifdef HAVE_GNUTLS_SYSTEM_KEYS
- if (key_is_sys) {
- vpn_progress(vpninfo, PRG_DEBUG,
- certinfo_string(certinfo, _("Using system key %s\n"),
- _("Using secondary system key %s\n")),
- certinfo->key);
- err = gnutls_privkey_init(&gci->pkey);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error initialising private key structure: %s\n"),
- gnutls_strerror(err));
- ret = -EIO;
- goto out;
- }
- gnutls_privkey_set_pin_function(gci->pkey, gnutls_pin_callback, certinfo);
- err = gnutls_privkey_import_url(gci->pkey, certinfo->key, 0);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error importing system key %s: %s\n"),
- certinfo->key, gnutls_strerror(err));
- ret = -EIO;
- goto out;
- }
- goto match_cert;
- }
- #endif /* HAVE_GNUTLS_SYSTEM_KEYS */
- #if defined(HAVE_P11KIT)
- if (key_is_p11) {
- vpn_progress(vpninfo, PRG_TRACE,
- _("Trying PKCS#11 key URL %s\n"), key_url);
- err = gnutls_pkcs11_privkey_init(&p11key);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error initialising PKCS#11 key structure: %s\n"),
- gnutls_strerror(err));
- ret = -EIO;
- goto out;
- }
- gnutls_pkcs11_privkey_set_pin_function(p11key, gnutls_pin_callback, certinfo);
- err = gnutls_pkcs11_privkey_import_url(p11key, key_url, 0);
- /* Annoyingly, some tokens don't even admit the *existence* of
- the key until they're logged in. And thus a search doesn't
- work unless it specifies the *token* too. But if the URI for
- key and cert are the same, and the cert was found, then we
- can work out what token the *cert* was found in and try that
- before we give up... */
- if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE &&
- certinfo->cert == certinfo->key) {
- gnutls_pkcs11_obj_t crt;
- P11KitUri *uri;
- CK_TOKEN_INFO *token;
- char buf[65];
- size_t s;
- if (gnutls_pkcs11_obj_init(&crt))
- goto key_err;
- if (gnutls_pkcs11_obj_import_url(crt, cert_url, 0))
- goto key_err_obj;
- uri = p11_kit_uri_new();
- if (!uri)
- goto key_err_obj;
- if (p11_kit_uri_parse(key_url, P11_KIT_URI_FOR_ANY, uri))
- goto key_err_uri;
- token = p11_kit_uri_get_token_info(uri);
- if (!token)
- goto key_err_uri;
- if (!token->label[0]) {
- s = sizeof(token->label) + 1;
- if (!gnutls_pkcs11_obj_get_info(crt, GNUTLS_PKCS11_OBJ_TOKEN_LABEL,
- buf, &s))
- fill_token_info(buf, s, token->label, sizeof(token->label));
- }
- if (!token->manufacturerID[0]) {
- s = sizeof(token->manufacturerID) + 1;
- if (!gnutls_pkcs11_obj_get_info(crt, GNUTLS_PKCS11_OBJ_TOKEN_MANUFACTURER,
- buf, &s))
- fill_token_info(buf, s, token->manufacturerID, sizeof(token->manufacturerID));
- }
- if (!token->model[0]) {
- s = sizeof(token->model) + 1;
- if (!gnutls_pkcs11_obj_get_info(crt, GNUTLS_PKCS11_OBJ_TOKEN_MODEL,
- buf, &s))
- fill_token_info(buf, s, token->model, sizeof(token->model));
- }
- if (!token->serialNumber[0]) {
- s = sizeof(token->serialNumber) + 1;
- if (!gnutls_pkcs11_obj_get_info(crt, GNUTLS_PKCS11_OBJ_TOKEN_SERIAL,
- buf, &s))
- fill_token_info(buf, s, token->serialNumber, sizeof(token->serialNumber));
- }
- free(key_url);
- key_url = NULL;
- if (p11_kit_uri_format(uri, P11_KIT_URI_FOR_ANY, &key_url))
- goto key_err_uri;
- vpn_progress(vpninfo, PRG_TRACE,
- _("Trying PKCS#11 key URL %s\n"), key_url);
- err = gnutls_pkcs11_privkey_import_url(p11key, key_url, 0);
- /* If it still doesn't work then try dropping CKA_LABEL and adding the
- CKA_ID of the cert. */
- if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE &&
- (p11_kit_uri_get_attribute(uri, CKA_LABEL) ||
- !p11_kit_uri_get_attribute(uri, CKA_ID))) {
- CK_ATTRIBUTE attr;
- s = sizeof(buf);
- if (gnutls_pkcs11_obj_get_info(crt, GNUTLS_PKCS11_OBJ_ID,
- buf, &s))
- goto key_err_uri;
- attr.type = CKA_ID;
- attr.pValue = buf;
- attr.ulValueLen = s;
- p11_kit_uri_set_attribute(uri, &attr);
- p11_kit_uri_clear_attribute(uri, CKA_LABEL);
- free(key_url);
- key_url = NULL;
- if (p11_kit_uri_format(uri, P11_KIT_URI_FOR_ANY, &key_url))
- goto key_err_uri;
- vpn_progress(vpninfo, PRG_TRACE,
- _("Trying PKCS#11 key URL %s\n"), key_url);
- err = gnutls_pkcs11_privkey_import_url(p11key, key_url, 0);
- }
- key_err_uri:
- p11_kit_uri_free(uri);
- key_err_obj:
- gnutls_pkcs11_obj_deinit(crt);
- key_err:
- ;
- }
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error importing PKCS#11 URL %s: %s\n"),
- key_url, gnutls_strerror(err));
- gnutls_pkcs11_privkey_deinit(p11key);
- ret = -EIO;
- goto out;
- }
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Using PKCS#11 key %s\n"), key_url);
- err = gnutls_privkey_init(&gci->pkey);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error initialising private key structure: %s\n"),
- gnutls_strerror(err));
- gnutls_pkcs11_privkey_deinit(p11key);
- ret = -EIO;
- goto out;
- }
- err = gnutls_privkey_import_pkcs11(gci->pkey, p11key, GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error importing PKCS#11 key into private key structure: %s\n"),
- gnutls_strerror(err));
- gnutls_pkcs11_privkey_deinit(p11key);
- ret = -EIO;
- goto out;
- }
- goto match_cert;
- }
- #endif /* HAVE_P11KIT */
- /* OK, not a PKCS#11 key so it must be coming from a file... load the
- file into memory, unless it's the same as the cert file and we
- already loaded that. */
- if (!fdata.data || certinfo->key != certinfo->cert) {
- gnutls_free(fdata.data);
- fdata.data = NULL;
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Using private key file %s\n"), certinfo->key);
- ret = load_datum(vpninfo, &fdata, certinfo->key);
- if (ret)
- goto out;
- }
- /* Is it a PEM file with a TPM key blob? */
- if (strstr((char *)fdata.data, "-----BEGIN TSS KEY BLOB-----")) {
- #ifndef HAVE_TROUSERS
- vpn_progress(vpninfo, PRG_ERR,
- _("This version of OpenConnect was built without TPM support\n"));
- return -EINVAL;
- #else
- ret = load_tpm1_key(vpninfo, certinfo, &fdata, &gci->pkey, &pkey_sig);
- if (ret)
- goto out;
- goto match_cert;
- #endif
- }
- /* Is it a PEM file with a TPM key blob? */
- if (strstr((char *)fdata.data, "-----BEGIN TSS2 PRIVATE KEY-----") ||
- strstr((char *)fdata.data, "-----BEGIN TSS2 KEY BLOB-----")) {
- #ifndef HAVE_TSS2
- vpn_progress(vpninfo, PRG_ERR,
- _("This version of OpenConnect was built without TPM2 support\n"));
- return -EINVAL;
- #else
- ret = load_tpm2_key(vpninfo, certinfo, &fdata, &gci->pkey, &pkey_sig);
- if (ret)
- goto out;
- goto match_cert;
- #endif
- }
- /* OK, try other PEM files... */
- gnutls_x509_privkey_init(&x509key);
- if ((pem_header = strstr((char *)fdata.data, "-----BEGIN RSA PRIVATE KEY-----")) ||
- (pem_header = strstr((char *)fdata.data, "-----BEGIN DSA PRIVATE KEY-----")) ||
- (pem_header = strstr((char *)fdata.data, "-----BEGIN EC PRIVATE KEY-----"))) {
- /* PKCS#1 files, including OpenSSL's odd encrypted version */
- char type = pem_header[11];
- char *p = strchr(pem_header, '\n');
- if (!p) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to interpret PEM file\n"));
- ret = -EINVAL;
- goto out;
- }
- while (*p == '\n' || *p == '\r')
- p++;
- if (!strncmp(p, "Proc-Type: 4,ENCRYPTED", 22)) {
- p += 22;
- while (*p == '\n' || *p == '\r')
- p++;
- ret = import_openssl_pem(vpninfo, certinfo, x509key, type, p,
- fdata.size - (p - (char *)fdata.data));
- if (ret)
- goto out;
- } else {
- err = gnutls_x509_privkey_import(x509key, &fdata, GNUTLS_X509_FMT_PEM);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to load PKCS#1 private key: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out;
- }
- }
- } else if (strstr((char *)fdata.data, "-----BEGIN PRIVATE KEY-----")) {
- /* Unencrypted PKCS#8 */
- err = gnutls_x509_privkey_import_pkcs8(x509key, &fdata,
- GNUTLS_X509_FMT_PEM,
- NULL, GNUTLS_PKCS_PLAIN);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to load private key as PKCS#8: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out;
- }
- } else if (strstr((char *)fdata.data, "-----BEGIN ENCRYPTED PRIVATE KEY-----")) {
- /* Encrypted PKCS#8 */
- char *pass = certinfo->password;
- while ((err = gnutls_x509_privkey_import_pkcs8(x509key, &fdata,
- GNUTLS_X509_FMT_PEM,
- pass?:"", 0))) {
- if (err != GNUTLS_E_DECRYPTION_FAILED) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to load private key as PKCS#8: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out;
- }
- certinfo->password = NULL;
- if (pass) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to decrypt PKCS#8 certificate file\n"));
- free_pass(&pass);
- }
- err = request_passphrase(vpninfo, "openconnect_pem",
- &pass, _("Enter PEM pass phrase:"));
- if (err) {
- ret = -EINVAL;
- goto out;
- }
- }
- free_pass(&pass);
- certinfo->password = NULL;
- } else if (!gnutls_x509_privkey_import(x509key, &fdata, GNUTLS_X509_FMT_DER) ||
- !gnutls_x509_privkey_import_pkcs8(x509key, &fdata, GNUTLS_X509_FMT_DER,
- NULL, GNUTLS_PKCS_PLAIN)) {
- /* Unencrypted DER (PKCS#1 or PKCS#8) */
- } else {
- /* Last chance: try encrypted PKCS#8 DER. And give up if it's not that */
- char *pass = certinfo->password;
- while ((err = gnutls_x509_privkey_import_pkcs8(x509key, &fdata,
- GNUTLS_X509_FMT_DER,
- pass?:"", 0))) {
- if (err != GNUTLS_E_DECRYPTION_FAILED) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to determine type of private key %s\n"),
- certinfo->key);
- ret = -EINVAL;
- goto out;
- }
- certinfo->password = NULL;
- if (pass) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to decrypt PKCS#8 certificate file\n"));
- free_pass(&pass);
- }
- err = request_passphrase(vpninfo, "openconnect_pem",
- &pass, _("Enter PKCS#8 pass phrase:"));
- if (err) {
- ret = -EINVAL;
- goto out;
- }
- }
- free_pass(&pass);
- certinfo->password = NULL;
- }
- /* Now attempt to make sure we use the *correct* certificate, to match
- the key. Since we have a software key, we can easily query it and
- compare its key_id with each certificate till we find a match. */
- err = gnutls_x509_privkey_get_key_id(x509key, 0, key_id, &key_id_size);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to get key ID: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out;
- }
- /* If extra_certs[] is NULL, we have one candidate in 'cert' to check. */
- for (i = 0; i < (extra_certs ? nr_extra_certs : 1); i++) {
- unsigned char cert_id[20];
- size_t cert_id_size = sizeof(cert_id);
- err = gnutls_x509_crt_get_key_id(extra_certs ? extra_certs[i] : cert, 0, cert_id, &cert_id_size);
- if (err)
- continue;
- if (cert_id_size == key_id_size && !memcmp(cert_id, key_id, key_id_size)) {
- if (extra_certs) {
- cert = extra_certs[i];
- extra_certs[i] = NULL;
- }
- goto got_key;
- }
- }
- /* There's no pkey (there's an x509 key), so even if p11-kit or trousers is
- enabled we'll fall straight through the bit at match_cert: below, and go
- directly to the bit where it prints the 'no match found' error and exits. */
- #if defined(HAVE_P11KIT) || defined(HAVE_TROUSERS) || defined(HAVE_TSS2) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- match_cert:
- /* If we have a privkey from PKCS#11 or TPM, we can't do the simple comparison
- of key ID that we do for software keys to find which certificate is a
- match. So sign some dummy data and then check the signature against each
- of the available certificates until we find the right one. */
- if (gci->pkey) {
- /* The TPM code may have already signed it, to test authorisation. We
- only sign here for PKCS#11 keys, in which case fdata might be
- empty too so point it at dummy data. We try multiple hashes
- because depending on the algorithm or device not all may be usable */
- unsigned j;
- gnutls_digest_algorithm_t *dig, digs[] = {
- GNUTLS_DIG_SHA256, GNUTLS_DIG_SHA1,
- GNUTLS_DIG_SHA512, GNUTLS_DIG_UNKNOWN
- };
- for (dig = digs; *dig != GNUTLS_DIG_UNKNOWN; dig++) {
- if (!pkey_sig.data) {
- if (!fdata.data) {
- fdata.data = dummy_hash_data;
- fdata.size = 20;
- }
- err = gnutls_privkey_sign_data(gci->pkey, *dig, 0,
- &fdata, &pkey_sig);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error signing test data with private key: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out;
- }
- }
- /* If extra_certs[] is NULL, we have one candidate in 'cert' to check. */
- for (j = 0; j < (extra_certs ? nr_extra_certs : 1); j++) {
- gnutls_pubkey_t pubkey = NULL;
- err = gnutls_pubkey_init(&pubkey);
- if (err >= 0)
- err = gnutls_pubkey_import_x509(pubkey, extra_certs ? extra_certs[j] : cert, 0);
- if (err < 0) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error validating signature against certificate: %s\n"),
- gnutls_strerror(err));
- /* We'll probably fail shortly if we don't find it. */
- gnutls_pubkey_deinit(pubkey);
- continue;
- }
- err = verify_signed_data(pubkey, gci->pkey, *dig,
- &fdata, &pkey_sig);
- gnutls_pubkey_deinit(pubkey);
- if (err >= 0) {
- if (extra_certs) {
- cert = extra_certs[j];
- extra_certs[j] = NULL;
- }
- gnutls_free(pkey_sig.data);
- pkey_sig.data = NULL;
- goto got_key;
- }
- }
- gnutls_free(pkey_sig.data);
- pkey_sig.data = NULL;
- }
- }
- #endif /* P11KIT || TROUSERS || TSS2 || SYSTEM_KEYS */
- /* We shouldn't reach this. It means that we didn't find *any* matching cert */
- vpn_progress(vpninfo, PRG_ERR,
- certinfo_string(certinfo, _("No SSL certificate found to match private key\n"),
- _("No secondary certificate found to match private key\n")));
- ret = -EINVAL;
- goto out;
- /********************************************************************/
- got_key:
- /* Now we have a key in either 'x509key' or 'gci->pkey', a matching cert in 'cert',
- and potentially a list of other certs in 'extra_certs[]'. If we loaded
- a PKCS#12 file we may have a trust chain in 'gci->certs[]' too. */
- if (!((!gci->pkey != !x509key) && cert))
- vpn_progress(vpninfo, PRG_ERR,
- _("got_key conditions not met!\n"));
- /* Transform the x509key to abstract key */
- if (!gci->pkey) {
- err = gnutls_privkey_init(&gci->pkey);
- if (err >= 0) {
- gnutls_privkey_set_pin_function(gci->pkey, gnutls_pin_callback,
- certinfo);
- err = gnutls_privkey_import_x509(gci->pkey, x509key,
- GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE);
- }
- if (err < 0) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Error creating an abstract privkey from /x509_privkey: %s\n"),
- gnutls_strerror(err));
- /* gci->pkey will be freed in out */
- ret = -EIO;
- goto out;
- }
- }
- /* pkey owns x509key */
- x509key = NULL;
- check_certificate_expiry(vpninfo, certinfo, cert);
- get_cert_name(cert, name, sizeof(name));
- vpn_progress(vpninfo, PRG_INFO,
- certinfo_string(certinfo, _("Using client certificate '%s'\n"),
- _("Using secondary certificate '%s'\n")),
- name);
- /* OpenSSL has problems with certificate chains — if there are
- multiple certs with the same name, it doesn't necessarily
- choose the _right_ one. (RT#1942)
- Pick the right ones for ourselves and add them manually. */
- /* We may have already got a bunch of certs from PKCS#12
- file. Remember how many need to be freed when we're done,
- since we'll expand the gci->certs array with more
- from the cafile and extra_certs[] array if we can, and
- those extra certs must not be freed (twice). */
- if (!gci->nr_certs) {
- gci->certs = gnutls_malloc(sizeof(*gci->certs));
- if (!gci->certs) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to allocate memory for certificate\n"));
- ret = -ENOMEM;
- goto out;
- }
- gci->certs[0] = cert;
- gci->nr_certs = 1;
- }
- last_cert = gci->certs[gci->nr_certs-1];
- while (1) {
- gnutls_x509_crt_t issuer = NULL;
- gnutls_x509_crt_t *saved_cert_list = NULL;
- for (i = 0; i < nr_extra_certs; i++) {
- if (extra_certs[i] &&
- gnutls_x509_crt_check_issuer(last_cert, extra_certs[i]))
- break;
- }
- if (i < nr_extra_certs) {
- /* We found the next cert in the chain in extra_certs[] */
- issuer = extra_certs[i];
- extra_certs[i] = NULL;
- } else {
- /* Look for it in the system trust cafile too. */
- err = gnutls_certificate_get_issuer(vpninfo->https_cred,
- last_cert, &issuer, 0);
- /* GnuTLS 3.2.10 does not support flag GNUTLS_TL_GET_COPY,
- * which makes a copy of the issuer. Therefore, we make
- * an explicit copy of the certificate
- */
- if (err >= 0)
- err = copy_cert(&issuer, issuer);
- #ifdef HAVE_P11KIT
- if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE && cert_is_p11) {
- gnutls_datum_t t = {NULL, 0};
- err = gnutls_pkcs11_get_raw_issuer(cert_url, last_cert, &t, GNUTLS_X509_FMT_DER, 0);
- if (err >= 0) {
- err = import_cert(&issuer, &t);
- gnutls_free(t.data);
- }
- if (err < 0) {
- vpn_progress(vpninfo, PRG_TRACE,
- _("Got no issuer from PKCS#11\n"));
- } else {
- get_cert_name(issuer, name, sizeof(name));
- vpn_progress(vpninfo, PRG_ERR,
- _("Got next CA '%s' from PKCS#11\n"), name);
- }
- }
- #endif
- if (err)
- break;
- }
- if (gnutls_x509_crt_check_issuer(issuer, issuer)) {
- /* Don't actually include the root CA. If they don't already trust it,
- then handing it to them isn't going to help. But don't omit the
- original certificate if it's self-signed. */
- gnutls_x509_crt_deinit(issuer);
- break;
- }
- /* OK, we found a new cert to add to our chain. */
- saved_cert_list = gci->certs;
- gci->certs = gnutls_realloc(gci->certs,
- sizeof(cert) * (gci->nr_certs+1));
- if (!gci->certs) {
- gci->certs = saved_cert_list;
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to allocate memory for supporting certificates\n"));
- gnutls_x509_crt_deinit(issuer);
- break;
- }
- /* Append the new one */
- gci->certs[gci->nr_certs] = issuer;
- gci->nr_certs++;
- last_cert = issuer;
- }
- for (i = 1; i < gci->nr_certs; i++) {
- get_cert_name(gci->certs[i], name, sizeof(name));
- vpn_progress(vpninfo, PRG_DEBUG,
- _("Adding supporting CA '%s'\n"), name);
- }
- ret = 0;
- if ((flags & MULTICERT_COMPAT))
- (void) check_multicert_compat(vpninfo, certinfo);
- /* OK, now we've checked the cert expiry and warned the user if it's
- going to expire soon, and we've built up as much of a trust chain
- in gci->certs[] as we can find, to help the server work around
- OpenSSL RT#1942. Set up the GnuTLS credentials with the appropriate
- key and certs. GnuTLS makes us do this differently for X509 privkeys
- vs. TPM/PKCS#11 "generic" privkeys, and the latter is particularly
- 'fun' for GnuTLS 2.12... */
- out:
- gnutls_x509_privkey_deinit(x509key);
- if (cert && !gci->certs) {
- /* Not if gci->certs. It's gci->certs[0] then and will be
- * freed as such. This is only for the error path. */
- gnutls_x509_crt_deinit(cert);
- }
- for (i = 0; i < nr_extra_certs; i++) {
- if (extra_certs[i])
- gnutls_x509_crt_deinit(extra_certs[i]);
- }
- gnutls_free(extra_certs);
- #if defined(HAVE_P11KIT) || defined(HAVE_TROUSERS) || defined(HAVE_TSS2) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- /* If we support arbitrary privkeys, we might have abused fdata.data
- just to point to something to hash. Don't free it in that case! */
- if (fdata.data != dummy_hash_data)
- #endif
- gnutls_free(fdata.data);
- #ifdef HAVE_P11KIT
- /* This exists in the HAVE_GNUTLS_SYSTEM_KEYS case but will never
- change so it's OK not to add to the #ifdef mess here. */
- if (cert_url != certinfo->cert)
- free(cert_url);
- if (key_url != certinfo->key)
- free(key_url);
- #endif
- if (ret)
- unload_certificate(certinfo, 1);
- return ret;
- }
- /* We have to convert the array of X509 certificates to gnutls_pcert_st
- for ourselves. There's no function that takes a gnutls_privkey_t as
- the key and gnutls_x509_crt_t certificates. */
- static int assign_privkey(struct openconnect_info *vpninfo, struct gtls_cert_info *gci)
- {
- gnutls_pcert_st *pcerts = gnutls_calloc(gci->nr_certs, sizeof(*pcerts));
- unsigned int i;
- int err;
- if (!pcerts)
- return GNUTLS_E_MEMORY_ERROR;
- for (i = 0 ; i < gci->nr_certs; i++) {
- err = gnutls_pcert_import_x509(pcerts + i, gci->certs[i], 0);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Importing X509 certificate failed: %s\n"),
- gnutls_strerror(err));
- goto free_pcerts;
- }
- }
- err = gnutls_certificate_set_key(vpninfo->https_cred, NULL, 0,
- pcerts, gci->nr_certs, gci->pkey);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Setting PKCS#11 certificate failed: %s\n"),
- gnutls_strerror(err));
- free_pcerts:
- for (i = 0 ; i < gci->nr_certs; i++)
- gnutls_pcert_deinit(pcerts + i);
- } else
- gci->pkey = NULL; /* We gave it away */
- free(pcerts);
- return err;
- }
- static int load_primary_certificate(struct openconnect_info *vpninfo)
- {
- struct cert_info *certinfo = &vpninfo->certinfo[0];
- int ret, err;
- ret = load_certificate(vpninfo, certinfo, 0);
- if (ret)
- return ret;
- struct gtls_cert_info *gci = certinfo->priv_info;
- gnutls_x509_crt_t cert = gci->certs[0];
- get_cert_md5_fingerprint(vpninfo, cert, vpninfo->local_cert_md5);
- if (gci->crl) {
- err = gnutls_certificate_set_x509_crl(vpninfo->https_cred, &gci->crl, 1);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Setting certificate revocation list failed: %s\n"),
- gnutls_strerror(err));
- ret = -EINVAL;
- goto out;
- }
- }
- #if GNUTLS_VERSION_NUMBER >= 0x030600
- if (gnutls_privkey_get_pk_algorithm(gci->pkey, NULL) == GNUTLS_PK_RSA) {
- /*
- * For hardware RSA keys, we need to check if they can cope with PSS.
- * If not, disable TLSv1.3 which would make PSS mandatory.
- * https://bugzilla.redhat.com/show_bug.cgi?id=1663058
- */
- gnutls_datum_t fdata= { (void *)gci, sizeof(*gci) };
- gnutls_datum_t pkey_sig = { NULL, 0 };
- err = gnutls_privkey_sign_data2(gci->pkey, GNUTLS_SIGN_RSA_PSS_RSAE_SHA256, 0, &fdata, &pkey_sig);
- if (err) {
- vpn_progress(vpninfo, PRG_INFO,
- _("Private key appears not to support RSA-PSS. Disabling TLSv1.3\n"));
- vpninfo->no_tls13 = 1;
- }
- free(pkey_sig.data);
- }
- #endif
- err = assign_privkey(vpninfo, gci);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Setting certificate failed: %s\n"),
- gnutls_strerror(err));
- ret = -EIO;
- } else
- ret = 0;
- out:
- unload_certificate(certinfo, ret < 0);
- return ret;
- }
- static int get_cert_fingerprint(struct openconnect_info *vpninfo,
- gnutls_x509_crt_t cert,
- gnutls_digest_algorithm_t algo,
- char *buf)
- {
- unsigned char md[256];
- size_t md_size = sizeof(md);
- unsigned int i;
- if (gnutls_x509_crt_get_fingerprint(cert, algo, md, &md_size))
- return -EIO;
- for (i = 0; i < md_size; i++)
- sprintf(&buf[i*2], "%02X", md[i]);
- return 0;
- }
- int get_cert_md5_fingerprint(struct openconnect_info *vpninfo,
- void *cert, char *buf)
- {
- return get_cert_fingerprint(vpninfo, cert, GNUTLS_DIG_MD5, buf);
- }
- static int set_peer_cert_hash(struct openconnect_info *vpninfo)
- {
- size_t shalen;
- gnutls_pubkey_t pkey;
- gnutls_datum_t d;
- int err;
- err = gnutls_pubkey_init(&pkey);
- if (err)
- return err;
- err = gnutls_pubkey_import_x509(pkey, vpninfo->peer_cert, 0);
- if (!err)
- err = gnutls_pubkey_export2(pkey, GNUTLS_X509_FMT_DER, &d);
- gnutls_pubkey_deinit(pkey);
- if (err)
- return err;
- shalen = sizeof(vpninfo->peer_cert_sha256_raw);
- err = gnutls_fingerprint(GNUTLS_DIG_SHA256, &d, vpninfo->peer_cert_sha256_raw, &shalen);
- if (err) {
- gnutls_free(d.data);
- return err;
- }
- shalen = sizeof(vpninfo->peer_cert_sha1_raw);
- err = gnutls_fingerprint(GNUTLS_DIG_SHA1, &d, vpninfo->peer_cert_sha1_raw, &shalen);
- if (err) {
- gnutls_free(d.data);
- return err;
- }
- gnutls_free(d.data);
- return 0;
- }
- char *openconnect_get_peer_cert_details(struct openconnect_info *vpninfo)
- {
- gnutls_datum_t buf;
- if (gnutls_x509_crt_print(vpninfo->peer_cert, GNUTLS_CRT_PRINT_FULL, &buf))
- return NULL;
- return (char *)buf.data;
- }
- int openconnect_get_peer_cert_DER(struct openconnect_info *vpninfo,
- unsigned char **buf)
- {
- size_t l = 0;
- unsigned char *ret = NULL;
- if (gnutls_x509_crt_export(vpninfo->peer_cert, GNUTLS_X509_FMT_DER,
- ret, &l) != GNUTLS_E_SHORT_MEMORY_BUFFER)
- return -EIO;
- ret = gnutls_malloc(l);
- if (!ret)
- return -ENOMEM;
- if (gnutls_x509_crt_export(vpninfo->peer_cert, GNUTLS_X509_FMT_DER,
- ret, &l)) {
- gnutls_free(ret);
- return -EIO;
- }
- *buf = ret;
- return l;
- }
- void openconnect_free_cert_info(struct openconnect_info *vpninfo,
- void *buf)
- {
- gnutls_free(buf);
- }
- int openconnect_get_peer_cert_chain(struct openconnect_info *vpninfo,
- struct oc_cert **chainp)
- {
- struct oc_cert *chain, *p;
- const gnutls_datum_t *cert_list = vpninfo->cert_list_handle;
- int i, cert_list_size = vpninfo->cert_list_size;
- if (!cert_list)
- return -EINVAL;
- if (cert_list_size <= 0)
- return -EIO;
- p = chain = calloc(cert_list_size, sizeof(struct oc_cert));
- if (!chain)
- return -ENOMEM;
- for (i = 0; i < cert_list_size; i++, p++) {
- p->der_data = (unsigned char *)cert_list[i].data;
- p->der_len = cert_list[i].size;
- }
- *chainp = chain;
- return cert_list_size;
- }
- void openconnect_free_peer_cert_chain(struct openconnect_info *vpninfo,
- struct oc_cert *chain)
- {
- free(chain);
- }
- static int verify_peer(gnutls_session_t session)
- {
- struct openconnect_info *vpninfo = gnutls_session_get_ptr(session);
- const gnutls_datum_t *cert_list;
- gnutls_x509_crt_t cert;
- unsigned int status, cert_list_size;
- const char *reason = NULL;
- int err = 0;
- cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
- if (!cert_list) {
- vpn_progress(vpninfo, PRG_ERR, _("Server presented no certificate\n"));
- return GNUTLS_E_CERTIFICATE_ERROR;
- }
- if (vpninfo->peer_cert) {
- unsigned char *prev_der = NULL;
- int der_len = openconnect_get_peer_cert_DER(vpninfo, &prev_der);
- if (der_len < 0) {
- vpn_progress(vpninfo, PRG_ERR, _("Error comparing server's cert on rehandshake: %s\n"),
- strerror(-der_len));
- return GNUTLS_E_CERTIFICATE_ERROR;
- }
- if (cert_list[0].size != der_len || memcmp(cert_list[0].data, prev_der, der_len)) {
- vpn_progress(vpninfo, PRG_ERR, _("Server presented different cert on rehandshake\n"));
- gnutls_free(prev_der);
- return GNUTLS_E_CERTIFICATE_ERROR;
- }
- gnutls_free(prev_der);
- vpn_progress(vpninfo, PRG_TRACE, _("Server presented identical cert on rehandshake\n"));
- return 0;
- }
- err = gnutls_x509_crt_init(&cert);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR, _("Error initialising X509 cert structure\n"));
- return GNUTLS_E_CERTIFICATE_ERROR;
- }
- err = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR, _("Error importing server's cert\n"));
- gnutls_x509_crt_deinit(cert);
- return GNUTLS_E_CERTIFICATE_ERROR;
- }
- vpninfo->peer_cert = cert;
- err = set_peer_cert_hash(vpninfo);
- if (err < 0) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Could not calculate hash of server's certificate\n"));
- }
- err = gnutls_certificate_verify_peers2(session, &status);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR, _("Error checking server cert status\n"));
- return GNUTLS_E_CERTIFICATE_ERROR;
- }
- if (status & GNUTLS_CERT_REVOKED)
- reason = _("certificate revoked");
- else if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
- reason = _("signer not found");
- else if (status & GNUTLS_CERT_SIGNER_NOT_CA)
- reason = _("signer not a CA certificate");
- else if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
- reason = _("insecure algorithm");
- else if (status & GNUTLS_CERT_NOT_ACTIVATED)
- reason = _("certificate not yet activated");
- else if (status & GNUTLS_CERT_EXPIRED)
- reason = _("certificate expired");
- else if (status & GNUTLS_CERT_INVALID)
- /* If this is set and no other reason, it apparently means
- that signature verification failed. Not entirely sure
- why we don't just set a bit for that too. */
- reason = _("signature verification failed");
- if (reason)
- goto done;
- if (!gnutls_x509_crt_check_hostname(cert, vpninfo->hostname)) {
- int i, ret;
- unsigned char addrbuf[sizeof(struct in6_addr)];
- unsigned char certaddr[sizeof(struct in6_addr)];
- size_t addrlen = 0, certaddrlen;
- /* gnutls_x509_crt_check_hostname() doesn't cope with IPv6 literals
- in URI form with surrounding [] so we must check for ourselves. */
- if (vpninfo->hostname[0] == '[' &&
- vpninfo->hostname[strlen(vpninfo->hostname)-1] == ']') {
- char *p = &vpninfo->hostname[strlen(vpninfo->hostname)-1];
- *p = 0;
- if (inet_pton(AF_INET6, vpninfo->hostname + 1, addrbuf) > 0)
- addrlen = 16;
- *p = ']';
- }
- #if GNUTLS_VERSION_NUMBER < 0x030306
- /* And before 3.3.6 it didn't check IP addresses at all. */
- else if (inet_pton(AF_INET, vpninfo->hostname, addrbuf) > 0)
- addrlen = 4;
- else if (inet_pton(AF_INET6, vpninfo->hostname, addrbuf) > 0)
- addrlen = 16;
- #endif
- if (!addrlen) {
- /* vpninfo->hostname was not a bare IP address. Nothing to do */
- goto badhost;
- }
- for (i = 0; ; i++) {
- certaddrlen = sizeof(certaddr);
- ret = gnutls_x509_crt_get_subject_alt_name(cert, i, certaddr,
- &certaddrlen, NULL);
- /* If this happens, it wasn't an IP address. */
- if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
- continue;
- if (ret < 0)
- break;
- if (ret != GNUTLS_SAN_IPADDRESS)
- continue;
- if (certaddrlen == addrlen && !memcmp(addrbuf, certaddr, addrlen))
- goto done;
- }
- badhost:
- reason = _("certificate does not match hostname");
- }
- done:
- if (reason) {
- vpn_progress(vpninfo, PRG_INFO,
- _("Server certificate verify failed: %s\n"),
- reason);
- if (vpninfo->validate_peer_cert) {
- vpninfo->cert_list_handle = (void *)cert_list;
- vpninfo->cert_list_size = cert_list_size;
- err = vpninfo->validate_peer_cert(vpninfo->cbdata,
- reason) ? GNUTLS_E_CERTIFICATE_ERROR : 0;
- vpninfo->cert_list_handle = NULL;
- } else
- err = GNUTLS_E_CERTIFICATE_ERROR;
- }
- return err;
- }
- #ifdef HAVE_HPKE_SUPPORT
- static int finished_fn(gnutls_session_t session, unsigned int htype, unsigned when,
- unsigned int incoming, const gnutls_datum_t *msg)
- {
- struct openconnect_info *vpninfo = gnutls_session_get_ptr(session);
- if (incoming)
- return 0;
- if (msg->size > sizeof(vpninfo->finished)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("TLS Finished message larger than expected (%u bytes)\n"),
- msg->size);
- vpninfo->finished_len = sizeof(vpninfo->finished);
- } else
- vpninfo->finished_len = msg->size;
- memcpy(vpninfo->finished, msg->data, vpninfo->finished_len);
- return 0;
- }
- #endif
- int openconnect_open_https(struct openconnect_info *vpninfo)
- {
- int ssl_sock = -1;
- int err;
- if (vpninfo->https_sess)
- return 0;
- if (vpninfo->peer_cert) {
- gnutls_x509_crt_deinit(vpninfo->peer_cert);
- vpninfo->peer_cert = NULL;
- }
- free(vpninfo->peer_cert_hash);
- vpninfo->peer_cert_hash = NULL;
- gnutls_free(vpninfo->cstp_cipher);
- vpninfo->cstp_cipher = NULL;
- ssl_sock = connect_https_socket(vpninfo);
- if (ssl_sock < 0)
- return ssl_sock;
- if (!vpninfo->https_cred) {
- gnutls_certificate_allocate_credentials(&vpninfo->https_cred);
- if (!vpninfo->no_system_trust)
- gnutls_certificate_set_x509_system_trust(vpninfo->https_cred);
- gnutls_certificate_set_verify_function(vpninfo->https_cred,
- verify_peer);
- #ifdef ANDROID_KEYSTORE
- if (vpninfo->cafile && !strncmp(vpninfo->cafile, "keystore:", 9)) {
- gnutls_datum_t datum;
- unsigned int nr_certs;
- err = load_datum(vpninfo, &datum, vpninfo->cafile);
- if (err < 0) {
- gnutls_certificate_free_credentials(vpninfo->https_cred);
- vpninfo->https_cred = NULL;
- return err;
- }
- /* For GnuTLS 3.x We should use gnutls_x509_crt_list_import2() */
- nr_certs = count_x509_certificates(&datum);
- if (nr_certs) {
- gnutls_x509_crt_t *certs;
- int i;
- certs = calloc(nr_certs, sizeof(*certs));
- if (!certs) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to allocate memory for cafile certs\n"));
- gnutls_free(datum.data);
- gnutls_certificate_free_credentials(vpninfo->https_cred);
- vpninfo->https_cred = NULL;
- closesocket(ssl_sock);
- return -ENOMEM;
- }
- err = gnutls_x509_crt_list_import(certs, &nr_certs, &datum,
- GNUTLS_X509_FMT_PEM, 0);
- gnutls_free(datum.data);
- if (err >= 0) {
- nr_certs = err;
- err = gnutls_certificate_set_x509_trust(vpninfo->https_cred,
- certs, nr_certs);
- }
- for (i = 0; i < nr_certs; i++)
- gnutls_x509_crt_deinit(certs[i]);
- free(certs);
- if (err < 0) {
- /* From crt_list_import or set_x509_trust */
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to read certs from cafile: %s\n"),
- gnutls_strerror(err));
- gnutls_certificate_free_credentials(vpninfo->https_cred);
- vpninfo->https_cred = NULL;
- closesocket(ssl_sock);
- return -EINVAL;
- }
- }
- } else
- #endif
- if (vpninfo->cafile) {
- err = gnutls_certificate_set_x509_trust_file(vpninfo->https_cred,
- vpninfo->cafile,
- GNUTLS_X509_FMT_PEM);
- if (err < 0) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to open CA file '%s': %s\n"),
- vpninfo->cafile, gnutls_strerror(err));
- gnutls_certificate_free_credentials(vpninfo->https_cred);
- vpninfo->https_cred = NULL;
- closesocket(ssl_sock);
- return -EINVAL;
- }
- }
- if (vpninfo->certinfo[0].cert) {
- err = load_primary_certificate(vpninfo);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Loading certificate failed. Aborting.\n"));
- gnutls_certificate_free_credentials(vpninfo->https_cred);
- vpninfo->https_cred = NULL;
- closesocket(ssl_sock);
- return err;
- }
- }
- }
- gnutls_init(&vpninfo->https_sess, GNUTLS_CLIENT|GNUTLS_FORCE_CLIENT_CERT);
- gnutls_session_set_ptr(vpninfo->https_sess, (void *) vpninfo);
- /*
- * For versions of GnuTLS older than 3.2.9, we try to avoid long
- * packets by silently disabling extensions such as SNI.
- *
- * See comments above regarding COMPAT and DUMBFW.
- */
- if (string_is_hostname(vpninfo->hostname))
- gnutls_server_name_set(vpninfo->https_sess, GNUTLS_NAME_DNS,
- vpninfo->hostname,
- strlen(vpninfo->hostname));
- /*
- * If a ClientHello is between 256 and 511 bytes, the
- * server cannot distinguish between a SSLv2 formatted
- * packet and a SSLv3 formatted packet.
- *
- * F5 BIG-IP reverse proxies in particular will
- * silently drop an ambiguous ClientHello.
- *
- * GnuTLS fixes this in v3.2.9+ by padding ClientHello
- * packets to at least 512 bytes if %COMPAT or %DUMBFW
- * is specified.
- *
- * Discussion:
- * https://www.ietf.org/mail-archive/web/tls/current/msg10423.html
- *
- * GnuTLS commits:
- * b6d29bb1737f96ac44a8ef9cc9fe7f9837e20465
- * a9bd8c4d3a639c40adb964349297f891f583a21b
- * 531bec47037e882af32963f8461988f8c724919e
- * 7c45ebbdd877cd994b6b938bd6faef19558a01e1
- * 8d28901a3ebd2589d0fc9941475d50f04047f6fe
- * 28065ce3896b1b0f87972d0bce9b17641ebb69b9
- */
- if (!vpninfo->ciphersuite_config) {
- struct oc_text_buf *buf = buf_alloc();
- #ifdef DEFAULT_PRIO
- buf_append(buf, "%s", DEFAULT_PRIO ":%COMPAT");
- #else
- /* GnuTLS 3.5.19 and onward remove AES-CBC-HMAC-SHA256 from NORMAL,
- * but some Cisco servers can't do anything better, so
- * explicitly add '+SHA256' to allow it. Yay Cisco.
- * - GnuTLS commit that removed: c433cdf92349afae66c703bdacedf987f423605e
- * - Old server requiring SHA256: https://gitlab.com/openconnect/openconnect/-/issues/21
- *
- * Likewise, GnuTLS 3.6.0 and onward remove 3DES-CBC from NORMAL,
- * but some ancient servers can't do anything better. This (and ARCFOUR-128)
- * should not be re-enabled by default due to serious security flaws, so adding as
- * an option, --allow-insecure-crypto. Yay ancient, unpatched servers.
- * - GnuTLS commit that removed: 66f2a0a271bcc10e8fb68771f9349a3d3ecf6dda
- * - Old server requiring 3DES-CBC: https://gitlab.com/openconnect/openconnect/-/issues/145
- */
- buf_append(buf, "NORMAL:-VERS-SSL3.0:+SHA256:%%COMPAT");
- #endif
- if (vpninfo->pfs)
- buf_append(buf, ":-RSA");
- if (vpninfo->no_tls13)
- buf_append(buf, ":-VERS-TLS1.3");
- if (vpninfo->allow_insecure_crypto) {
- buf_append(buf, ":+3DES-CBC:+ARCFOUR-128:+SHA1");
- if (gnutls_check_version_numeric(3,6,0))
- buf_append(buf, ":%%VERIFY_ALLOW_SIGN_WITH_SHA1");
- } else
- buf_append(buf, ":-3DES-CBC:-ARCFOUR-128");
- if (buf_error(buf)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to construct GnuTLS priority string\n"));
- return buf_free(buf);
- }
- vpninfo->ciphersuite_config = buf->data;
- buf->data = NULL;
- buf_free(buf);
- }
- err = gnutls_priority_set_direct(vpninfo->https_sess,
- vpninfo->ciphersuite_config, NULL);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to set GnuTLS priority string (\"%s\"): %s\n"),
- vpninfo->ciphersuite_config, gnutls_strerror(err));
- gnutls_deinit(vpninfo->https_sess);
- vpninfo->https_sess = NULL;
- closesocket(ssl_sock);
- return -EIO;
- }
- gnutls_record_disable_padding(vpninfo->https_sess);
- gnutls_credentials_set(vpninfo->https_sess, GNUTLS_CRD_CERTIFICATE, vpninfo->https_cred);
- gnutls_transport_set_ptr(vpninfo->https_sess,(gnutls_transport_ptr_t)(intptr_t)ssl_sock);
- vpn_progress(vpninfo, PRG_INFO, _("SSL negotiation with %s\n"),
- vpninfo->hostname);
- #ifdef GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT
- gnutls_handshake_set_timeout(vpninfo->https_sess,
- GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
- #endif
- #ifdef HAVE_HPKE_SUPPORT
- /*
- * The AnyConnect STRAP protocol needs the Finished message from the
- * TLS connection. It isn't clear if this is a misguided attempt at
- * MITM protection or just a convenient nonce known to both sides.
- */
- gnutls_handshake_set_hook_function(vpninfo->https_sess, GNUTLS_HANDSHAKE_FINISHED,
- GNUTLS_HOOK_POST, finished_fn);
- #endif
- err = cstp_handshake(vpninfo, 1);
- if (err)
- return err;
- vpninfo->ssl_fd = ssl_sock;
- vpninfo->ssl_read = openconnect_gnutls_read;
- vpninfo->ssl_write = openconnect_gnutls_write;
- vpninfo->ssl_gets = openconnect_gnutls_gets;
- return 0;
- }
- int cstp_handshake(struct openconnect_info *vpninfo, unsigned init)
- {
- int err;
- int ssl_sock = -1;
- ssl_sock = (intptr_t)gnutls_transport_get_ptr(vpninfo->https_sess);
- while ((err = gnutls_handshake(vpninfo->https_sess))) {
- if (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
- fd_set rd_set, wr_set;
- int maxfd = ssl_sock;
- FD_ZERO(&rd_set);
- FD_ZERO(&wr_set);
- if (gnutls_record_get_direction(vpninfo->https_sess))
- FD_SET(ssl_sock, &wr_set);
- else
- FD_SET(ssl_sock, &rd_set);
- cmd_fd_set(vpninfo, &rd_set, &maxfd);
- if (select(maxfd + 1, &rd_set, &wr_set, NULL, NULL) < 0 &&
- errno != EINTR) {
- vpn_perror(vpninfo, _("Failed select() for TLS"));
- return -EIO;
- }
- if (is_cancel_pending(vpninfo, &rd_set)) {
- vpn_progress(vpninfo, PRG_ERR, _("SSL connection cancelled\n"));
- gnutls_deinit(vpninfo->https_sess);
- vpninfo->https_sess = NULL;
- closesocket(ssl_sock);
- return -EINTR;
- }
- } else if (gnutls_error_is_fatal(err)) {
- vpn_progress(vpninfo, PRG_ERR, _("SSL connection failure: %s\n"),
- gnutls_strerror(err));
- gnutls_deinit(vpninfo->https_sess);
- vpninfo->https_sess = NULL;
- closesocket(ssl_sock);
- return -EIO;
- } else {
- /* non-fatal error or warning. Ignore it and continue */
- vpn_progress(vpninfo, PRG_DEBUG,
- _("GnuTLS non-fatal return during handshake: %s\n"),
- gnutls_strerror(err));
- }
- }
- gnutls_free(vpninfo->cstp_cipher);
- vpninfo->cstp_cipher = get_gnutls_cipher(vpninfo->https_sess);
- if (init) {
- vpn_progress(vpninfo, PRG_INFO, _("Connected to HTTPS on %s with ciphersuite %s\n"),
- vpninfo->hostname, vpninfo->cstp_cipher);
- } else {
- vpn_progress(vpninfo, PRG_INFO, _("Renegotiated SSL on %s with ciphersuite %s\n"),
- vpninfo->hostname, vpninfo->cstp_cipher);
- }
- return 0;
- }
- void openconnect_close_https(struct openconnect_info *vpninfo, int final)
- {
- if (vpninfo->https_sess) {
- gnutls_deinit(vpninfo->https_sess);
- vpninfo->https_sess = NULL;
- }
- if (vpninfo->ssl_fd != -1) {
- unmonitor_fd(vpninfo, ssl);
- closesocket(vpninfo->ssl_fd);
- vpninfo->ssl_fd = -1;
- }
- if (final && vpninfo->https_cred) {
- gnutls_certificate_free_credentials(vpninfo->https_cred);
- vpninfo->https_cred = NULL;
- unload_certificate(&vpninfo->certinfo[0], 1);
- }
- }
- int openconnect_init_ssl(void)
- {
- #ifdef _WIN32
- int ret = openconnect__win32_sock_init();
- if (ret)
- return ret;
- #endif
- if (gnutls_global_init())
- return -EIO;
- return 0;
- }
- char *get_gnutls_cipher(gnutls_session_t session)
- {
- return gnutls_session_get_desc(session);
- }
- int openconnect_sha1(unsigned char *result, void *data, int datalen)
- {
- const gnutls_datum_t d = { data, datalen };
- size_t shalen = SHA1_SIZE;
- if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &d, result, &shalen))
- return -1;
- return 0;
- }
- int openconnect_sha256(unsigned char *result, void *data, int datalen)
- {
- const gnutls_datum_t d = { data, datalen };
- size_t shalen = SHA256_SIZE;
- if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &d, result, &shalen))
- return -1;
- return 0;
- }
- int openconnect_md5(unsigned char *result, void *data, int datalen)
- {
- const gnutls_datum_t d = { data, datalen };
- size_t md5len = MD5_SIZE;
- if (gnutls_fingerprint(GNUTLS_DIG_MD5, &d, result, &md5len))
- return -1;
- return 0;
- }
- int openconnect_random(void *bytes, int len)
- {
- if (gnutls_rnd(GNUTLS_RND_RANDOM, bytes, len))
- return -EIO;
- return 0;
- }
- int openconnect_local_cert_md5(struct openconnect_info *vpninfo,
- char *buf)
- {
- memcpy(buf, vpninfo->local_cert_md5, sizeof(vpninfo->local_cert_md5));
- return 0;
- }
- #if defined(HAVE_P11KIT) || defined(HAVE_GNUTLS_SYSTEM_KEYS)
- static int gnutls_pin_callback(void *priv, int attempt, const char *uri,
- const char *token_label, unsigned int flags,
- char *pin, size_t pin_max)
- {
- struct cert_info *certinfo = priv;
- struct openconnect_info *vpninfo = certinfo->vpninfo;
- struct pin_cache **cache = &vpninfo->pin_cache;
- struct oc_auth_form f;
- struct oc_form_opt o;
- char message[1024];
- int ret;
- if (!vpninfo || !vpninfo->process_auth_form)
- return -1;
- while (*cache) {
- if (!strcmp(uri, (*cache)->token)) {
- if ((*cache)->pin) {
- if (attempt == 0) {
- snprintf(pin, pin_max, "%s", (*cache)->pin);
- return 0;
- }
- memset((*cache)->pin, 0x5a, strlen((*cache)->pin));
- free((*cache)->pin);
- (*cache)->pin = NULL;
- }
- break;
- }
- cache = &(*cache)->next;
- }
- if (!*cache) {
- *cache = calloc(1, sizeof(struct pin_cache));
- if (!*cache)
- return -1;
- (*cache)->token = strdup(uri);
- }
- if (!attempt && certinfo->password) {
- snprintf(pin, pin_max, "%s", certinfo->password);
- (*cache)->pin = certinfo->password;
- certinfo->password = NULL;
- return 0;
- }
- memset(&f, 0, sizeof(f));
- f.auth_id = (char *)certinfo_string(certinfo, "pkcs11_pin",
- "secondary_pkcs11_pin");
- f.opts = &o;
- message[sizeof(message)-1] = 0;
- snprintf(message, sizeof(message) - 1, _("PIN required for %s"), token_label);
- f.message = message;
- if (flags & GNUTLS_PIN_WRONG)
- f.error = (char *)_("Wrong PIN");
- if (flags & GNUTLS_PIN_FINAL_TRY)
- f.banner = (char *)_("This is the final try before locking!");
- else if (flags & GNUTLS_PIN_COUNT_LOW)
- f.banner = (char *)_("Only a few tries left before locking!");
- o.next = NULL;
- o.type = OC_FORM_OPT_PASSWORD;
- o.name = (char *)"pkcs11_pin";
- o.label = (char *)_("Enter PIN:");
- o._value = NULL;
- ret = process_auth_form(vpninfo, &f);
- if (ret || !o._value)
- return -1;
- snprintf(pin, pin_max, "%s", o._value);
- (*cache)->pin = o._value;
- return 0;
- }
- #endif /* HAVE_P11KIT || HAVE_GNUTLS_SYSTEM_KEYS */
- #ifdef HAVE_LIBPCSCLITE
- int openconnect_hash_yubikey_password(struct openconnect_info *vpninfo,
- const char *password, int pwlen,
- const void *ident, int id_len)
- {
- unsigned char U[SHA1_SIZE];
- gnutls_hmac_hd_t dgst;
- int ret = -EIO;
- int i, j;
- if (gnutls_hmac_init(&dgst, GNUTLS_MAC_SHA1, password, pwlen))
- return -EIO;
- if (gnutls_hmac(dgst, ident, id_len))
- goto out;
- /* This is a subset of full PBKDF2, where we know the outer loop is only
- * run once because our output length (16) is less than the hash output
- * size (20). So just hard-code the value. */
- if (gnutls_hmac(dgst, "\x0\x0\x0\x1", 4))
- goto out;
- gnutls_hmac_output(dgst, U);
- memcpy(vpninfo->yubikey_pwhash, U, 16);
- for (i = 1; i < 1000; i++) {
- if (gnutls_hmac(dgst, U, SHA1_SIZE))
- goto out;
- gnutls_hmac_output(dgst, U);
- for (j = 0; j < 16; j++)
- vpninfo->yubikey_pwhash[j] ^= U[j];
- }
- ret = 0;
- out:
- gnutls_hmac_deinit(dgst, NULL);
- return ret;
- }
- int openconnect_yubikey_chalresp(struct openconnect_info *vpninfo,
- const void *challenge, int chall_len, void *result)
- {
- if (gnutls_hmac_fast(GNUTLS_MAC_SHA1, vpninfo->yubikey_pwhash, 16, challenge, chall_len, result))
- return -EIO;
- return 0;
- }
- #endif
- int hotp_hmac(struct openconnect_info *vpninfo, const void *challenge)
- {
- int ret;
- int hpos;
- unsigned char hash[64]; /* Enough for a SHA256 */
- gnutls_mac_algorithm_t alg;
- switch(vpninfo->oath_hmac_alg) {
- case OATH_ALG_HMAC_SHA1:
- alg = GNUTLS_MAC_SHA1;
- hpos = 19;
- break;
- case OATH_ALG_HMAC_SHA256:
- alg = GNUTLS_MAC_SHA256;
- hpos = 31;
- break;
- case OATH_ALG_HMAC_SHA512:
- alg = GNUTLS_MAC_SHA512;
- hpos = 63;
- break;
- default:
- vpn_progress(vpninfo, PRG_ERR,
- _("Unsupported OATH HMAC algorithm\n"));
- return -EINVAL;
- }
- ret = gnutls_hmac_fast(alg, vpninfo->oath_secret,
- vpninfo->oath_secret_len,
- challenge, 8, hash);
- if (ret) {
- vpninfo->progress(vpninfo, PRG_ERR,
- _("Failed to calculate OATH HMAC: %s\n"),
- gnutls_strerror(ret));
- return -EINVAL;
- }
- hpos = hash[hpos] & 15;
- return load_be32(&hash[hpos]) & 0x7fffffff;
- }
- static int ttls_pull_timeout_func(gnutls_transport_ptr_t t, unsigned int ms)
- {
- struct openconnect_info *vpninfo = t;
- vpn_progress(vpninfo, PRG_TRACE, _("%s %dms\n"), __func__, ms);
- return 0;
- }
- static ssize_t ttls_pull_func(gnutls_transport_ptr_t t, void *buf, size_t len)
- {
- int ret = pulse_eap_ttls_recv(t, buf, len);
- if (ret >= 0)
- return ret;
- else
- return GNUTLS_E_PULL_ERROR;
- }
- static ssize_t ttls_push_func(gnutls_transport_ptr_t t, const void *buf, size_t len)
- {
- int ret = pulse_eap_ttls_send(t, buf, len);
- if (ret >= 0)
- return ret;
- else
- return GNUTLS_E_PUSH_ERROR;
- }
- void *establish_eap_ttls(struct openconnect_info *vpninfo)
- {
- gnutls_session_t ttls_sess = NULL;
- int err;
- gnutls_init(&ttls_sess, GNUTLS_CLIENT);
- gnutls_session_set_ptr(ttls_sess, (void *) vpninfo);
- gnutls_transport_set_ptr(ttls_sess, (void *) vpninfo);
- gnutls_transport_set_push_function(ttls_sess, ttls_push_func);
- gnutls_transport_set_pull_function(ttls_sess, ttls_pull_func);
- gnutls_transport_set_pull_timeout_function(ttls_sess, ttls_pull_timeout_func);
- gnutls_credentials_set(ttls_sess, GNUTLS_CRD_CERTIFICATE, vpninfo->https_cred);
- err = gnutls_priority_set_direct(ttls_sess,
- vpninfo->ciphersuite_config, NULL);
- if (err < 0) {
- vpn_progress(vpninfo, PRG_TRACE,
- _("Could not set ciphersuites: %s\n"), vpninfo->ciphersuite_config);
- goto fail;
- }
- err = gnutls_handshake(ttls_sess);
- if (!err) {
- vpn_progress(vpninfo, PRG_TRACE,
- _("Established EAP-TTLS session\n"));
- return ttls_sess;
- }
- fail:
- gnutls_deinit(ttls_sess);
- return NULL;
- }
- void destroy_eap_ttls(struct openconnect_info *vpninfo, void *sess)
- {
- gnutls_deinit(sess);
- }
- static int generate_strap_key(gnutls_privkey_t *key, char **pubkey,
- gnutls_datum_t *privder_in,
- gnutls_datum_t *pubder)
- {
- int bits, pk, err;
- gnutls_privkey_t lkey = NULL;
- gnutls_pubkey_t pkey = NULL;
- gnutls_datum_t pdata = { };
- struct oc_text_buf *buf = NULL;
- #if GNUTLS_VERSION_NUMBER >= 0x030500
- pk = gnutls_ecc_curve_get_pk(GNUTLS_ECC_CURVE_SECP256R1);
- #else
- pk = GNUTLS_PK_EC;
- #endif
- bits = GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1);
- err = gnutls_privkey_init(&lkey);
- if (err)
- goto out;
- if (privder_in)
- err = gnutls_privkey_import_x509_raw(lkey, privder_in, GNUTLS_X509_FMT_DER,
- NULL, 0);
- else
- err = gnutls_privkey_generate(lkey, pk, bits, 0);
- if (err)
- goto out;
- err = gnutls_pubkey_init(&pkey);
- if (err)
- goto out;
- err = gnutls_pubkey_import_privkey(pkey, lkey,
- GNUTLS_KEY_KEY_AGREEMENT, 0);
- if (err)
- goto out;
- err = gnutls_pubkey_export2(pkey, GNUTLS_X509_FMT_DER, &pdata);
- if (err)
- goto out;
- buf = buf_alloc();
- buf_append_base64(buf, pdata.data, pdata.size, 0);
- if (buf_error(buf)) {
- err = GNUTLS_E_MEMORY_ERROR;
- goto out;
- }
- gnutls_privkey_deinit(*key);
- *key = lkey;
- free(*pubkey);
- *pubkey = buf->data;
- buf->data = NULL;
- out:
- buf_free(buf);
- gnutls_pubkey_deinit(pkey);
- if (err) {
- gnutls_privkey_deinit(lkey);
- *key = NULL;
- *pubkey = NULL;
- pubder = NULL; /* So we don't return it... */
- }
- if (pubder)
- *pubder = pdata;
- else
- gnutls_free(pdata.data);
- return err;
- }
- int generate_strap_keys(struct openconnect_info *vpninfo)
- {
- int err;
- err = generate_strap_key(&vpninfo->strap_key, &vpninfo->strap_pubkey, NULL, NULL);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to generate STRAP key: %s\n"),
- gnutls_strerror(err));
- free_strap_keys(vpninfo);
- return -EIO;
- }
- err = generate_strap_key(&vpninfo->strap_dh_key, &vpninfo->strap_dh_pubkey, NULL, NULL);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to generate STRAP DH key: %s\n"),
- gnutls_strerror(err));
- free_strap_keys(vpninfo);
- return -EIO;
- }
- return 0;
- }
- void free_strap_keys(struct openconnect_info *vpninfo)
- {
- if (vpninfo->strap_key)
- gnutls_privkey_deinit(vpninfo->strap_key);
- if (vpninfo->strap_dh_key)
- gnutls_privkey_deinit(vpninfo->strap_dh_key);
- vpninfo->strap_key = vpninfo->strap_dh_key = NULL;
- }
- #ifdef HAVE_HPKE_SUPPORT
- #include <nettle/ecc.h>
- #include <nettle/ecc-curve.h>
- int ecdh_compute_secp256r1(struct openconnect_info *vpninfo, const unsigned char *pubkey_der,
- int pubkey_len, unsigned char *secret)
- {
- int err, ret = -EIO;
- gnutls_pubkey_t pubkey;
- gnutls_datum_t d = { (void *)pubkey_der, pubkey_len };
- if ((err = gnutls_pubkey_init(&pubkey)) ||
- (err = gnutls_pubkey_import(pubkey, &d, GNUTLS_X509_FMT_DER))) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to decode server DH key: %s\n"),
- gnutls_strerror(err));
- goto out_pubkey;
- }
- /* Yay, we have to do ECDH for ourselves. */
- gnutls_datum_t pub_x, pub_y, priv_k;
- gnutls_ecc_curve_t pub_curve, priv_curve;
- if ((err = gnutls_privkey_export_ecc_raw(vpninfo->strap_dh_key, &priv_curve,
- NULL, NULL, &priv_k))) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to export DH private key parameters: %s\n"),
- gnutls_strerror(err));
- goto out_pubkey;
- }
- if ((err = gnutls_pubkey_export_ecc_raw(pubkey, &pub_curve, &pub_x, &pub_y))) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to export server DH key parameters: %s\n"),
- gnutls_strerror(err));
- goto out_priv_data;
- }
- if (pub_curve != GNUTLS_ECC_CURVE_SECP256R1 ||
- priv_curve != GNUTLS_ECC_CURVE_SECP256R1) {
- vpn_progress(vpninfo, PRG_ERR,
- _("HPKE uses unsupported EC curve (%d, %d)\n"),
- pub_curve, priv_curve);
- goto out_pub_data;
- }
- mpz_t mx, my;
- nettle_mpz_init_set_str_256_u(mx, pub_x.size, pub_x.data);
- nettle_mpz_init_set_str_256_u(my, pub_y.size, pub_y.data);
- struct ecc_point point;
- ecc_point_init(&point, nettle_get_secp_256r1());
- if (!ecc_point_set(&point, mx, my)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to create ECC public point for ECDH\n"));
- goto out_point;
- }
- mpz_t mk;
- nettle_mpz_init_set_str_256_u(mk, priv_k.size, priv_k.data);
- struct ecc_scalar priv;
- ecc_scalar_init(&priv, nettle_get_secp_256r1());
- ecc_scalar_set(&priv, mk);
- ecc_point_mul(&point, &priv, &point);
- ecc_point_get(&point, mx, my);
- nettle_mpz_get_str_256(32, secret, mx);
- ret = 0;
- ecc_scalar_clear(&priv);
- mpz_clear(mk);
- out_point:
- ecc_point_clear(&point);
- mpz_clear(mx);
- mpz_clear(my);
- out_pub_data:
- gnutls_free(pub_x.data);
- gnutls_free(pub_y.data);
- out_priv_data:
- gnutls_free(priv_k.data);
- out_pubkey:
- gnutls_pubkey_deinit(pubkey);
- return ret;
- }
- int hkdf_sha256_extract_expand(struct openconnect_info *vpninfo, unsigned char *buf,
- const unsigned char *info, int infolen)
- {
- const gnutls_datum_t d = { buf, SHA256_SIZE };
- int err = gnutls_hkdf_extract(GNUTLS_MAC_SHA256, &d, NULL, buf);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("HKDF extract failed: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- const gnutls_datum_t info_d = { (unsigned char *)info, infolen };
- err = gnutls_hkdf_expand(GNUTLS_MAC_SHA256, &d, &info_d, d.data, d.size);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("HKDF expand failed: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- return 0;
- }
- int aes_256_gcm_decrypt(struct openconnect_info *vpninfo, unsigned char *key,
- unsigned char *data, int len,
- unsigned char *iv, unsigned char *tag)
- {
- gnutls_cipher_hd_t h = NULL;
- const gnutls_datum_t d = { key, SHA256_SIZE };
- const gnutls_datum_t iv_d = { iv, 12 };
- int err = gnutls_cipher_init(&h, GNUTLS_CIPHER_AES_256_GCM, &d, &iv_d);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to init AES-256-GCM cipher: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- err = gnutls_cipher_decrypt(h, data, len);
- if (err) {
- dec_fail:
- vpn_progress(vpninfo, PRG_ERR,
- _("SSO token decryption failed: %s\n"),
- gnutls_strerror(err));
- gnutls_cipher_deinit(h);
- return -EIO;
- }
- /* Reusing the key buffer to fetch the auth tag */
- err = gnutls_cipher_tag(h, d.data, 12);
- if (err)
- goto dec_fail;
- if (memcmp(d.data, tag, 12)) {
- err = GNUTLS_E_MAC_VERIFY_FAILED;
- goto dec_fail;
- }
- gnutls_cipher_deinit(h);
- return 0;
- }
- void append_strap_privkey(struct openconnect_info *vpninfo,
- struct oc_text_buf *buf)
- {
- gnutls_x509_privkey_t xk = NULL;
- gnutls_datum_t d = { NULL, 0 };
- if (!gnutls_privkey_export_x509(vpninfo->strap_key, &xk) &&
- !gnutls_x509_privkey_export2(xk, GNUTLS_X509_FMT_DER, &d)) {
- buf_append_base64(buf, d.data, d.size, 0);
- gnutls_free(d.data);
- }
- gnutls_x509_privkey_deinit(xk);
- }
- int ingest_strap_privkey(struct openconnect_info *vpninfo,
- unsigned char *der, int len)
- {
- gnutls_datum_t d = { der, len };
- int err = generate_strap_key(&vpninfo->strap_key, &vpninfo->strap_pubkey, &d, NULL);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to decode STRAP key: %s\n"),
- gnutls_strerror(err));
- return -EIO;
- }
- return 0;
- }
- void append_strap_verify(struct openconnect_info *vpninfo,
- struct oc_text_buf *buf, int rekey)
- {
- gnutls_privkey_t sign_key = vpninfo->strap_key;
- int err;
- /* Concatenate our Finished message with our pubkey to be signed */
- struct oc_text_buf *nonce = buf_alloc();
- buf_append_bytes(nonce, vpninfo->finished, vpninfo->finished_len);
- if (rekey) {
- /* We have a copy and we don't want it freed just yet */
- vpninfo->strap_key = NULL;
- gnutls_datum_t pubkey_der;
- err = generate_strap_key(&vpninfo->strap_key, &vpninfo->strap_pubkey,
- NULL, &pubkey_der);
- if (err) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to regenerate STRAP key: %s\n"),
- gnutls_strerror(err));
- vpninfo->strap_key = sign_key;
- if (!buf_error(buf))
- buf->error = -EIO;
- buf_free(nonce);
- return;
- }
- buf_append_bytes(nonce, pubkey_der.data, pubkey_der.size);
- gnutls_free(pubkey_der.data);
- } else {
- int len;
- unsigned char *der = openconnect_base64_decode(&len, vpninfo->strap_pubkey);
- if (!der) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to generate STRAP key DER\n"));
- if (!buf_error(buf))
- buf->error = -EIO;
- buf_free(nonce);
- return;
- }
- buf_append_bytes(nonce, der, len);
- free(der);
- }
- err = GNUTLS_E_MEMORY_ERROR;
- if (buf_error(nonce)) {
- buf_free(nonce);
- goto fail;
- }
- gnutls_datum_t nd = { (void *)nonce->data, nonce->pos };
- gnutls_datum_t sig = { NULL, 0 };
- err = gnutls_privkey_sign_data(sign_key, GNUTLS_DIG_SHA256,
- 0, &nd, &sig);
- if (rekey)
- gnutls_privkey_deinit(sign_key);
- buf_free(nonce);
- if (err) {
- fail:
- vpn_progress(vpninfo, PRG_ERR, _("STRAP signature failed: %s\n"),
- gnutls_strerror(err));
- if (!buf_error(buf))
- buf->error = -EIO;
- return;
- }
- buf_append_base64(buf, sig.data, sig.size, 0);
- gnutls_free(sig.data);
- }
- #endif /* HAVE_HPKE_SUPPORT */
- /**
- * multiple-client certificate authentication
- */
- static int app_error(int err)
- {
- if (err >= 0)
- return 0;
- switch (err) {
- case GNUTLS_E_MEMORY_ERROR:
- return -ENOMEM;
- case GNUTLS_E_ILLEGAL_PARAMETER:
- case GNUTLS_E_INVALID_REQUEST:
- return -EINVAL;
- case GNUTLS_E_CONSTRAINT_ERROR:
- case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM:
- default:
- return -EIO;
- }
- }
- static int to_text_buf(struct oc_text_buf **bufp,
- const gnutls_datum_t *datum)
- {
- struct oc_text_buf *buf;
- *bufp = NULL;
- if (!(datum->size <= INT_MAX))
- return GNUTLS_E_MEMORY_ERROR;
- buf = buf_alloc();
- if (!buf)
- return GNUTLS_E_MEMORY_ERROR;
- buf_append_bytes(buf, datum->data, (int) datum->size);
- if (buf_error(buf) < 0)
- goto fail;
- *bufp = buf;
- return 0;
- fail:
- buf_free(buf);
- return GNUTLS_E_MEMORY_ERROR;
- }
- static int check_multicert_compat(struct openconnect_info *vpninfo,
- struct cert_info *certinfo)
- {
- #ifndef GNUTLS_KP_ANY
- # define GNUTLS_KP_ANY "2.5.29.37.0"
- #endif
- #ifndef GNUTLS_KP_TLS_WWW_CLIENT
- # define GNUTLS_KP_TLS_WWW_CLIENT "1.3.6.1.5.5.7.3.2"
- #endif
- #ifndef GNUTLS_KP_MS_SMART_CARD_LOGON
- # define GNUTLS_KP_MS_SMART_CARD_LOGON "1.3.6.1.4.1.311.20.2.2"
- #endif
- #define MAX_OID 128
- char oid[MAX_OID];
- struct gtls_cert_info *gci = certinfo->priv_info;
- gnutls_x509_crt_t crt;
- unsigned int usage = 0, critical;
- gnutls_pk_algorithm_t pk;
- size_t kp;
- int err;
- /**
- * Multiple certificate authentication protocol parametrizes the
- * digest independently of the pk algorithm. Warn if the signature
- * algorithm doesn't operate this way. Warn if this isn't so.
- */
- crt = gci->certs[0];
- pk = gnutls_x509_crt_get_pk_algorithm(crt, NULL);
- switch (pk) {
- default:
- vpn_progress(vpninfo, PRG_INFO,
- _("Certificate may be multiple certificate authentication incompatible.\n"));
- break;
- case GNUTLS_PK_RSA:
- #if GNUTLS_VERSION_NUMBER >= 0x030600
- case GNUTLS_PK_RSA_PSS:
- #endif
- case GNUTLS_PK_DSA:
- #if GNUTLS_VERSION_NUMBER >= 0x030500
- case GNUTLS_PK_ECDSA:
- #else
- case GNUTLS_PK_EC:
- #endif
- break;
- }
- /**
- * Now check if the certificate supports client authentication.
- *
- * extendedKeyUsage of either any, clientAuth, or msSmartcardLogin
- * satisfy authentication purposes.
- */
- for (kp = 0; ; kp++) {
- size_t oid_size = sizeof(oid);
- err = gnutls_x509_crt_get_key_purpose_oid(crt, kp,
- oid, &oid_size,
- &critical);
- if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
- /* EOF */
- break;
- } else if (err == GNUTLS_E_SHORT_MEMORY_BUFFER) {
- /**
- * The oids we are concerned with have length less than
- * MAX_OID
- */
- continue;
- } else if (err < 0) {
- vpn_progress(vpninfo, PRG_DEBUG,
- _("gnutls_x509_crt_get_key_purpose_oid: %s.\n"),
- gnutls_strerror(err));
- return -1;
- }
- if (strcmp(oid, GNUTLS_KP_ANY) == 0 ||
- strcmp(oid, GNUTLS_KP_TLS_WWW_CLIENT) == 0 ||
- strcmp(oid, GNUTLS_KP_MS_SMART_CARD_LOGON) == 0)
- return 1;
- }
- /**
- * The certificate does not specify extendedKeyUsage; try
- * keyUsage.
- */
- if (kp == 0) {
- /**
- * keyUsage of digitalSignature, nonRepudiation, or
- * both satisfy authenticatio.n
- */
- err = gnutls_x509_crt_get_key_usage(crt, &usage, &critical);
- if (err < 0 && err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
- vpn_progress(vpninfo, PRG_DEBUG,
- _("gnutls_X509_crt_get_key_usage: %s.\n"),
- gnutls_strerror(err));
- }
- if (err < 0)
- usage = 0;
- if (usage&
- (GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_NON_REPUDIATION))
- return 1;
- }
- /**
- * extendedKeyUsage, keyUsage, or both are specified, but
- * purposes are incompatible for authentication.
- */
- if (kp > 0 || usage != 0) {
- vpn_progress(vpninfo, PRG_INFO,
- _("The certificate specifies key usages incompatible with authentication.\n"));
- return 0;
- }
- /**
- * Found neither keyUsage nor extendedKeyUsage, defaults to any
- * purpose.
- */
- vpn_progress(vpninfo, PRG_INFO,
- _("Certificate doesn't specify key usage.\n"));
- return 1;
- }
- int export_certificate_pkcs7(struct openconnect_info *vpninfo,
- struct cert_info *certinfo,
- cert_format_t format,
- struct oc_text_buf **pp7b)
- {
- struct gtls_cert_info *gci = certinfo->priv_info;
- gnutls_pkcs7_t pkcs7;
- gnutls_datum_t data;
- gnutls_x509_crt_fmt_t certform;
- int err;
- if (!gci) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Precondition failed %s[%s]:%d\n"),
- __FILE__, __func__, __LINE__);
- return -EINVAL;
- }
- *pp7b = NULL;
- /**
- * Note! The PKCS7 structure produced by GnuTLS and this code is
- * different from protocol captures in three ways:
- *
- * x: The root certificate is not added.
- *
- * x: The first object of the signedData is a OBJECT IDENTIFIER
- * digestedData (1 2 840 113549 1 7 5), instead of OBJECT IDENTIFIER
- * data (1 2 840 113549 1 7 1) and zero-length * OCTET STRING.
- *
- * x: Certificates are ordered in ASN.1 canonical order instead of the
- * order added. The practical consequence is the server must identity
- * the user certificate (a certificate for which basicConstraints
- * CA:TRUE is false) and reconstruct the certificate path.
- *
- * Testing has not shown that these differences are meaningful, but the
- * future will tell.
- */
- err = gnutls_pkcs7_init(&pkcs7);
- if (err < 0)
- goto error;
- for (size_t i = 0, ncerts = gci->nr_certs; i < ncerts; i++) {
- err = gnutls_pkcs7_set_crt(pkcs7, gci->certs[i]);
- if (err < 0)
- goto error;
- }
- if (format == CERT_FORMAT_ASN1) {
- certform = GNUTLS_X509_FMT_DER;
- } else if (format == CERT_FORMAT_PEM) {
- certform = GNUTLS_X509_FMT_PEM;
- } else {
- err = GNUTLS_E_INVALID_REQUEST;
- goto error;
- }
- err = gnutls_pkcs7_export2(pkcs7, certform, &data);
- if (err < 0)
- goto error;
- err = to_text_buf(pp7b, &data);
- gnutls_free(data.data);
- if (err < 0) {
- error:
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to generate the PKCS#7 structure: %s.\n"),
- gnutls_strerror(err));
- }
- gnutls_pkcs7_deinit(pkcs7);
- return app_error(err);
- }
- int multicert_sign_data(struct openconnect_info *vpninfo,
- struct cert_info *certinfo,
- unsigned int hashes,
- const void *data, size_t len,
- struct oc_text_buf **psig)
- {
- static const struct {
- openconnect_hash_type id;
- gnutls_digest_algorithm_t hash;
- } hash_map[] = {
- { OPENCONNECT_HASH_SHA512, GNUTLS_DIG_SHA512 },
- { OPENCONNECT_HASH_SHA384, GNUTLS_DIG_SHA384 },
- { OPENCONNECT_HASH_SHA256, GNUTLS_DIG_SHA256 },
- { OPENCONNECT_HASH_UNKNOWN, GNUTLS_DIG_UNKNOWN },
- };
- gnutls_datum_t datum = { (void *) data, len };
- gnutls_datum_t sign_data = { 0 };
- struct gtls_cert_info *gci = certinfo->priv_info;
- struct oc_text_buf *sig_buf = NULL;
- openconnect_hash_type hash;
- gnutls_pk_algorithm_t pk;
- gnutls_sign_algorithm_t sign;
- int ret, err;
- if (!(gci && data && len && psig)) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Precondition failed %s[%s]:%d.\n"),
- __FILE__, __func__, __LINE__);
- return -EINVAL;
- }
- err = gnutls_x509_crt_get_pk_algorithm(gci->certs[0], NULL);
- if (err < 0)
- goto error;
- pk = err;
- /**
- * Sign data using hashes with decreasing hash size as
- * Anyconnect prefers SHA2-512.
- */
- for (size_t i = 0;
- (hash = hash_map[i].id) != OPENCONNECT_HASH_UNKNOWN;
- i++) {
- if ((hashes & MULTICERT_HASH_FLAG(hash)) == 0)
- continue;
- sign = gnutls_pk_to_sign(pk, hash_map[i].hash);
- #if GNUTLS_VERSION_NUMBER >= 0x030600
- err = gnutls_privkey_sign_data2(gci->pkey,
- sign,
- /* flag */ 0, &datum, &sign_data);
- #else
- err = gnutls_privkey_sign_data(gci->pkey,
- gnutls_sign_get_hash_algorithm(sign),
- /* flag */ 0, &datum, &sign_data);
- #endif
- if (err < 0) {
- vpn_progress(vpninfo, PRG_DEBUG,
- _("gnutls_privkey_sign_data: %s.\n"),
- gnutls_strerror(err));
- if (err == GNUTLS_E_INVALID_REQUEST || err == GNUTLS_E_CONSTRAINT_ERROR)
- continue;
- goto error;
- }
- err = to_text_buf(&sig_buf, &sign_data);
- gnutls_free(sign_data.data);
- break;
- }
- /**
- * Since we tested for compatibility when we loaded the certificate,
- * this condition is unlikely to happen.
- */
- if (hash == OPENCONNECT_HASH_UNKNOWN)
- err = GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
- if (err < 0) {
- error:
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to sign data with second certificate: %s.\n"),
- gnutls_strerror(err));
- ret = app_error(err);
- goto done;
- }
- *psig = sig_buf;
- ret = hash;
- done:
- return ret;
- }
|