libdigidocpp
CNGSigner.cpp
Go to the documentation of this file.
1 /*
2  * libdigidocpp
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  */
19 
20 #include "CNGSigner.h"
21 
22 #include "../../log.h"
23 #include "../cert/X509Cert.h"
24 #include "../Digest.h"
25 #include "../../Conf.h"
26 #include "../../util/File.h"
27 
28 #include <algorithm>
29 #include <sstream>
30 #include <Windows.h>
31 #include <ncrypt.h>
32 #include <WinCrypt.h>
33 #include <cryptuiapi.h>
34 
35 extern "C" {
36 
37 typedef BOOL (WINAPI * PFNCCERTDISPLAYPROC)(
38  __in PCCERT_CONTEXT pCertContext,
39  __in HWND hWndSelCertDlg,
40  __in void *pvCallbackData
41 );
42 
44  DWORD dwSize;
45  HWND hwndParent;
46  DWORD dwFlags;
47  LPCWSTR szTitle;
49  LPCWSTR szDisplayString;
50  PFNCFILTERPROC pFilterCallback;
54  HCERTSTORE * rghDisplayStores;
55  DWORD cStores;
56  HCERTSTORE * rghStores;
58  LPCPROPSHEETPAGEW rgPropSheetPages;
59  HCERTSTORE hSelectedCertStore;
61 
64 
65 PCCERT_CONTEXT WINAPI CryptUIDlgSelectCertificateW(
67 );
68 
69 #define CryptUIDlgSelectCertificate CryptUIDlgSelectCertificateW
70 
71 } // extern "C"
72 
73 namespace digidoc
74 {
75 
76 typedef SECURITY_STATUS (WINAPI *s_NCryptFreeObject)( NCRYPT_HANDLE hObject );
77 typedef SECURITY_STATUS (WINAPI *s_NCryptSetProperty)( NCRYPT_HANDLE hObject,
78  LPCWSTR pszProperty, PBYTE pbInput, DWORD cbInput, DWORD dwFlags );
79 typedef SECURITY_STATUS (WINAPI *s_NCryptSignHash)( NCRYPT_KEY_HANDLE hKey,
80  VOID *pPaddingInfo, PBYTE pbHashValue, DWORD cbHashValue, PBYTE pbSignature,
81  DWORD cbSignature, DWORD *pcbResult, DWORD dwFlags );
82 
84 {
85 public:
86  static BOOL WINAPI CertFilter(PCCERT_CONTEXT cert_context,
87  BOOL* is_initial_selected_cert, void* callback_data);
88 
89  HINSTANCE h;
93 
95  NCRYPT_KEY_HANDLE key;
96  std::wstring pin;
98 };
99 
100 }
101 
102 using namespace digidoc;
103 
104 BOOL CNGSignerPrivate::CertFilter(PCCERT_CONTEXT cert_context,
105  BOOL *, void *callback_data)
106 {
107  int *counter = static_cast<int*>(callback_data);
108  X509Cert cert( std::vector<unsigned char>(cert_context->pbCertEncoded,
109  cert_context->pbCertEncoded+cert_context->cbCertEncoded));
110  std::vector<digidoc::X509Cert::KeyUsage> usage = cert.getKeyUsage();
111  if( find( usage.begin(), usage.end(), digidoc::X509Cert::NonRepudiation ) != usage.end() )
112  {
113  (*counter)++;
114  return true;
115  }
116  return false;
117 }
118 
125 CNGSigner::CNGSigner(const std::string &pin, bool selectFirst) throw(SignException)
126  : d(new CNGSignerPrivate)
127 {
128  d->h = LoadLibraryW(L"ncrypt.dll");
129  d->key = 0;
130  setPin(pin);
131  setSelectFirst(selectFirst);
132  if(!d->h)
133  return;
134  d->f_NCryptFreeObject = s_NCryptFreeObject(GetProcAddress(d->h, "NCryptFreeObject"));
135  d->f_NCryptSetProperty = s_NCryptSetProperty(GetProcAddress(d->h, "NCryptSetProperty"));
136  d->f_NCryptSignHash = s_NCryptSignHash(GetProcAddress(d->h, "NCryptSignHash"));
137 }
138 
143 {
144  if(d->h)
145  FreeLibrary(d->h);
146  delete d;
147 }
148 
159 X509* CNGSigner::getCert() const throw(SignException)
160 {
161  if(d->cert.handle())
162  return d->cert.handle();
163 
164  d->f_NCryptFreeObject( d->key );
165  HCERTSTORE store = CertOpenSystemStore(0, L"MY");
166  if(!store)
167  return 0;
168 
169  PCCERT_CONTEXT cert_context = 0;
170  if(d->selectFirst)
171  {
172  PCCERT_CONTEXT find = 0;
173  while(find = CertFindCertificateInStore(store, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, find))
174  {
175  BYTE keyUsage = 0;
176  CertGetIntendedKeyUsage(X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, find->pCertInfo, &keyUsage, 1);
177  if(keyUsage & CERT_NON_REPUDIATION_KEY_USAGE)
178  {
179  cert_context = find;
180  break;
181  }
182  }
183  }
184  else
185  {
186  int counter = 0;
187  CRYPTUI_SELECTCERTIFICATE_STRUCT pcsc = { sizeof(pcsc) };
189  pcsc.pvCallbackData = &counter;
190  pcsc.cDisplayStores = 1;
191  pcsc.rghDisplayStores = &store;
192  cert_context = CryptUIDlgSelectCertificate(&pcsc);
193  }
194  if(!cert_context)
195  THROW_SIGNEXCEPTION("No certificates selected");
196 
197  DWORD flags = CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG|CRYPT_ACQUIRE_COMPARE_KEY_FLAG;
198  //if( silent ) flags |= CRYPT_ACQUIRE_SILENT_FLAG;
199  DWORD spec = 0;
200  BOOL ncrypt = false;
201  CryptAcquireCertificatePrivateKey( cert_context, flags, 0, &d->key, &spec, &ncrypt );
202  if(!d->key)
203  THROW_SIGNEXCEPTION("Failed to aquire certificate private key");
204 
205  d->cert = X509Cert( std::vector<unsigned char>(cert_context->pbCertEncoded,
206  cert_context->pbCertEncoded+cert_context->cbCertEncoded));
207  CertFreeCertificateContext( cert_context );
208 
209  return d->cert.handle();
210 }
211 
212 void CNGSigner::setPin(const std::string &pin)
213 {
214  d->pin = util::File::encodeName(pin);
215 }
216 
218 {
219  d->selectFirst = first;
220 }
221 
231 void CNGSigner::sign(const Digest &digest, Signature &signature) throw(SignException)
232 {
233  DEBUG("sign(digest = {type=%s,digest=%p,length=%d}, signature={signature=%p,length=%d})",
234  OBJ_nid2sn(digest.type), digest.digest, digest.length, signature.signature, signature.length);
235 
236  if(!d->f_NCryptSignHash)
237  THROW_SIGNEXCEPTION("CNG library not loaded.");
238 
239  BCRYPT_PKCS1_PADDING_INFO padInfo;
240  padInfo.pszAlgId = 0;
241  switch(digest.type)
242  {
243  case NID_sha1: padInfo.pszAlgId = NCRYPT_SHA1_ALGORITHM; break;
244  case NID_sha224: padInfo.pszAlgId = L"SHA224"; break;
245  case NID_sha256: padInfo.pszAlgId = NCRYPT_SHA256_ALGORITHM; break;
246  case NID_sha384: padInfo.pszAlgId = NCRYPT_SHA384_ALGORITHM; break;
247  case NID_sha512: padInfo.pszAlgId = NCRYPT_SHA512_ALGORITHM; break;
248  case NID_md5_sha1: //padInfo.pszAlgId = L"SHAMD5"; break;
249  default: break;
250  }
251 
252  SECURITY_STATUS err = 0;
253  if(!d->pin.empty())
254  err = d->f_NCryptSetProperty(d->key, NCRYPT_PIN_PROPERTY, PBYTE(d->pin.c_str()), d->pin.size(), 0);
255 
256  err = d->f_NCryptSignHash(d->key, &padInfo, PBYTE(digest.digest), digest.length,
257  signature.signature, signature.length, (DWORD*)&signature.length, BCRYPT_PAD_PKCS1);
258 
259  switch(err)
260  {
261  case ERROR_SUCCESS: break;
262  case SCARD_W_CANCELLED_BY_USER:
263  {
264  SignException e(__FILE__, __LINE__, "PIN acquisition canceled.");
266  throw e;
267  break;
268  }
269  default:
270  std::ostringstream s;
271  s << "Failed to login to token: " << err;
272  SignException e(__FILE__, __LINE__, s.str());
273  e.setCode(Exception::PINFailed);
274  throw e;
275  break;
276  }
277 }
278 
280 {
281  int result = digidoc::Digest::toMethod( Conf::getInstance()->getSignatureUri() );
282  if( result == NID_sha1 )
283  return result;
284  bool found = false;
285  std::vector<std::string> pol = digidoc::X509Cert( getCert() ).getCertificatePolicies();
286  for( std::vector<std::string>::iterator i = pol.begin(); i != pol.end(); ++i )
287  {
288  if(i->find("1.3.6.1.4.1.10015.1.2.", 22) == 0 ||
289  i->find("1.3.6.1.4.1.10015.3.2.", 22) == 0)
290  found = true;
291  }
292  if(!found)
293  return X509Cert(getCert()).getPaddingSize() > 128 ? result : NID_sha224;
294  return result;
295 }