├── .github └── workflows │ ├── github-release.yml │ ├── test-macos-recent-go-versions.yml │ └── test-windows-recent-go-versions.yml ├── LICENSE.md ├── README.md ├── certstore.go ├── certstore_darwin.go ├── certstore_linux.go ├── certstore_test.go ├── certstore_windows.go ├── crypt_strings_windows.go ├── go.mod ├── go.sum ├── main_test.go └── main_windows_test.go /.github/workflows/github-release.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | Publish: 10 | permissions: 11 | contents: write 12 | runs-on: ubuntu-latest 13 | if: startsWith(github.ref, 'refs/tags/v') 14 | steps: 15 | - name: Calculate release name 16 | run: | 17 | GITHUB_REF=${{ github.ref }} 18 | RELEASE_NAME=${GITHUB_REF#"refs/tags/"} 19 | echo "RELEASE_NAME=${RELEASE_NAME}" >> $GITHUB_ENV 20 | - name: Publish release 21 | uses: actions/create-release@v1 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | tag_name: ${{ github.ref }} 26 | release_name: ${{ env.RELEASE_NAME }} 27 | draft: false 28 | prerelease: false 29 | -------------------------------------------------------------------------------- /.github/workflows/test-macos-recent-go-versions.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test macOS (recent Go versions) 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: ["1.14", "1.x"] 8 | # Doesn't work on Linux. 9 | os: [macos-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - name: Install Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: ${{ matrix.go-version }} 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | - name: Test 19 | run: go test -v ./... 20 | -------------------------------------------------------------------------------- /.github/workflows/test-windows-recent-go-versions.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test Windows (recent Go versions) 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: ["1.14", "1.x"] 8 | # Doesn't work on Linux. 9 | os: [windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - name: Install Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: ${{ matrix.go-version }} 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | - name: Test 19 | env: 20 | CGO_ENABLED: 1 21 | run: go test -v ./... 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ben Toews. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project has moved 2 | 3 | `certstore` is now part of the repository. 4 | 5 | Please update your dependencies from `github.com/github/certstore` to [`github.com/github/smimesign/certstore`](https://github.com/github/smimesign/tree/main/certstore) 6 | -------------------------------------------------------------------------------- /certstore.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "crypto" 5 | "crypto/x509" 6 | "errors" 7 | ) 8 | 9 | var ( 10 | // ErrUnsupportedHash is returned by Signer.Sign() when the provided hash 11 | // algorithm isn't supported. 12 | ErrUnsupportedHash = errors.New("unsupported hash algorithm") 13 | ) 14 | 15 | // Open opens the system's certificate store. 16 | func Open() (Store, error) { 17 | return openStore() 18 | } 19 | 20 | // Store represents the system's certificate store. 21 | type Store interface { 22 | // Identities gets a list of identities from the store. 23 | Identities() ([]Identity, error) 24 | 25 | // Import imports a PKCS#12 (PFX) blob containing a certificate and private 26 | // key. 27 | Import(data []byte, password string) error 28 | 29 | // Close closes the store. 30 | Close() 31 | } 32 | 33 | // Identity is a X.509 certificate and its corresponding private key. 34 | type Identity interface { 35 | // Certificate gets the identity's certificate. 36 | Certificate() (*x509.Certificate, error) 37 | 38 | // CertificateChain attempts to get the identity's full certificate chain. 39 | CertificateChain() ([]*x509.Certificate, error) 40 | 41 | // Signer gets a crypto.Signer that uses the identity's private key. 42 | Signer() (crypto.Signer, error) 43 | 44 | // Delete deletes this identity from the system. 45 | Delete() error 46 | 47 | // Close any manually managed memory held by the Identity. 48 | Close() 49 | } 50 | -------------------------------------------------------------------------------- /certstore_darwin.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | /* 4 | #cgo CFLAGS: -x objective-c 5 | #cgo LDFLAGS: -framework CoreFoundation -framework Security 6 | #include 7 | #include 8 | */ 9 | import "C" 10 | import ( 11 | "crypto" 12 | "crypto/ecdsa" 13 | "crypto/rsa" 14 | "crypto/x509" 15 | "errors" 16 | "fmt" 17 | "io" 18 | "unsafe" 19 | ) 20 | 21 | // work around https://golang.org/doc/go1.10#cgo 22 | // in go>=1.10 CFTypeRefs are translated to uintptrs instead of pointers. 23 | var ( 24 | nilCFDictionaryRef C.CFDictionaryRef 25 | nilSecCertificateRef C.SecCertificateRef 26 | nilCFArrayRef C.CFArrayRef 27 | nilCFDataRef C.CFDataRef 28 | nilCFErrorRef C.CFErrorRef 29 | nilCFStringRef C.CFStringRef 30 | nilSecIdentityRef C.SecIdentityRef 31 | nilSecKeyRef C.SecKeyRef 32 | nilCFAllocatorRef C.CFAllocatorRef 33 | ) 34 | 35 | // macStore is a bogus type. We have to explicitly open/close the store on 36 | // windows, so we provide those methods here too. 37 | type macStore int 38 | 39 | // openStore is a function for opening a macStore. 40 | func openStore() (macStore, error) { 41 | return macStore(0), nil 42 | } 43 | 44 | // Identities implements the Store interface. 45 | func (s macStore) Identities() ([]Identity, error) { 46 | query := mapToCFDictionary(map[C.CFTypeRef]C.CFTypeRef{ 47 | C.CFTypeRef(C.kSecClass): C.CFTypeRef(C.kSecClassIdentity), 48 | C.CFTypeRef(C.kSecReturnRef): C.CFTypeRef(C.kCFBooleanTrue), 49 | C.CFTypeRef(C.kSecMatchLimit): C.CFTypeRef(C.kSecMatchLimitAll), 50 | }) 51 | if query == nilCFDictionaryRef { 52 | return nil, errors.New("error creating CFDictionary") 53 | } 54 | defer C.CFRelease(C.CFTypeRef(query)) 55 | 56 | var absResult C.CFTypeRef 57 | if err := osStatusError(C.SecItemCopyMatching(query, &absResult)); err != nil { 58 | if err == errSecItemNotFound { 59 | return []Identity{}, nil 60 | } 61 | 62 | return nil, err 63 | } 64 | defer C.CFRelease(C.CFTypeRef(absResult)) 65 | 66 | // don't need to release aryResult since the abstract result is released above. 67 | aryResult := C.CFArrayRef(absResult) 68 | 69 | // identRefs aren't owned by us initially. newMacIdentity retains them. 70 | n := C.CFArrayGetCount(aryResult) 71 | identRefs := make([]C.CFTypeRef, n) 72 | C.CFArrayGetValues(aryResult, C.CFRange{0, n}, (*unsafe.Pointer)(unsafe.Pointer(&identRefs[0]))) 73 | 74 | idents := make([]Identity, 0, n) 75 | for _, identRef := range identRefs { 76 | idents = append(idents, newMacIdentity(C.SecIdentityRef(identRef))) 77 | } 78 | 79 | return idents, nil 80 | } 81 | 82 | // Import implements the Store interface. 83 | func (s macStore) Import(data []byte, password string) error { 84 | cdata, err := bytesToCFData(data) 85 | if err != nil { 86 | return err 87 | } 88 | defer C.CFRelease(C.CFTypeRef(cdata)) 89 | 90 | cpass := stringToCFString(password) 91 | defer C.CFRelease(C.CFTypeRef(cpass)) 92 | 93 | cops := mapToCFDictionary(map[C.CFTypeRef]C.CFTypeRef{ 94 | C.CFTypeRef(C.kSecImportExportPassphrase): C.CFTypeRef(cpass), 95 | }) 96 | if cops == nilCFDictionaryRef { 97 | return errors.New("error creating CFDictionary") 98 | } 99 | defer C.CFRelease(C.CFTypeRef(cops)) 100 | 101 | var cret C.CFArrayRef 102 | if err := osStatusError(C.SecPKCS12Import(cdata, cops, &cret)); err != nil { 103 | return err 104 | } 105 | defer C.CFRelease(C.CFTypeRef(cret)) 106 | 107 | return nil 108 | } 109 | 110 | // Close implements the Store interface. 111 | func (s macStore) Close() {} 112 | 113 | // macIdentity implements the Identity interface. 114 | type macIdentity struct { 115 | ref C.SecIdentityRef 116 | kref C.SecKeyRef 117 | cref C.SecCertificateRef 118 | crt *x509.Certificate 119 | chain []*x509.Certificate 120 | } 121 | 122 | func newMacIdentity(ref C.SecIdentityRef) *macIdentity { 123 | C.CFRetain(C.CFTypeRef(ref)) 124 | return &macIdentity{ref: ref} 125 | } 126 | 127 | // Certificate implements the Identity interface. 128 | func (i *macIdentity) Certificate() (*x509.Certificate, error) { 129 | certRef, err := i.getCertRef() 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | crt, err := exportCertRef(certRef) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | i.crt = crt 140 | 141 | return i.crt, nil 142 | } 143 | 144 | // CertificateChain implements the Identity interface. 145 | func (i *macIdentity) CertificateChain() ([]*x509.Certificate, error) { 146 | if i.chain != nil { 147 | return i.chain, nil 148 | } 149 | 150 | certRef, err := i.getCertRef() 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | policy := C.SecPolicyCreateSSL(0, nilCFStringRef) 156 | 157 | var trustRef C.SecTrustRef 158 | if err := osStatusError(C.SecTrustCreateWithCertificates(C.CFTypeRef(certRef), C.CFTypeRef(policy), &trustRef)); err != nil { 159 | return nil, err 160 | } 161 | defer C.CFRelease(C.CFTypeRef(trustRef)) 162 | 163 | var status C.SecTrustResultType 164 | if err := osStatusError(C.SecTrustEvaluate(trustRef, &status)); err != nil { 165 | return nil, err 166 | } 167 | 168 | var ( 169 | nchain = C.SecTrustGetCertificateCount(trustRef) 170 | chain = make([]*x509.Certificate, 0, int(nchain)) 171 | ) 172 | 173 | for i := C.CFIndex(0); i < nchain; i++ { 174 | // TODO: do we need to release these? 175 | chainCertref := C.SecTrustGetCertificateAtIndex(trustRef, i) 176 | if chainCertref == nilSecCertificateRef { 177 | return nil, errors.New("nil certificate in chain") 178 | } 179 | 180 | chainCert, err := exportCertRef(chainCertref) 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | chain = append(chain, chainCert) 186 | } 187 | 188 | i.chain = chain 189 | 190 | return chain, nil 191 | } 192 | 193 | // Signer implements the Identity interface. 194 | func (i *macIdentity) Signer() (crypto.Signer, error) { 195 | // pre-load the certificate so Public() is less likely to return nil 196 | // unexpectedly. 197 | if _, err := i.Certificate(); err != nil { 198 | return nil, err 199 | } 200 | 201 | return i, nil 202 | } 203 | 204 | // Delete implements the Identity interface. 205 | func (i *macIdentity) Delete() error { 206 | itemList := []C.SecIdentityRef{i.ref} 207 | itemListPtr := (*unsafe.Pointer)(unsafe.Pointer(&itemList[0])) 208 | citemList := C.CFArrayCreate(nilCFAllocatorRef, itemListPtr, 1, nil) 209 | if citemList == nilCFArrayRef { 210 | return errors.New("error creating CFArray") 211 | } 212 | defer C.CFRelease(C.CFTypeRef(citemList)) 213 | 214 | query := mapToCFDictionary(map[C.CFTypeRef]C.CFTypeRef{ 215 | C.CFTypeRef(C.kSecClass): C.CFTypeRef(C.kSecClassIdentity), 216 | C.CFTypeRef(C.kSecMatchItemList): C.CFTypeRef(citemList), 217 | }) 218 | if query == nilCFDictionaryRef { 219 | return errors.New("error creating CFDictionary") 220 | } 221 | defer C.CFRelease(C.CFTypeRef(query)) 222 | 223 | if err := osStatusError(C.SecItemDelete(query)); err != nil { 224 | return err 225 | } 226 | 227 | return nil 228 | } 229 | 230 | // Close implements the Identity interface. 231 | func (i *macIdentity) Close() { 232 | if i.ref != nilSecIdentityRef { 233 | C.CFRelease(C.CFTypeRef(i.ref)) 234 | i.ref = nilSecIdentityRef 235 | } 236 | 237 | if i.kref != nilSecKeyRef { 238 | C.CFRelease(C.CFTypeRef(i.kref)) 239 | i.kref = nilSecKeyRef 240 | } 241 | 242 | if i.cref != nilSecCertificateRef { 243 | C.CFRelease(C.CFTypeRef(i.cref)) 244 | i.cref = nilSecCertificateRef 245 | } 246 | } 247 | 248 | // Public implements the crypto.Signer interface. 249 | func (i *macIdentity) Public() crypto.PublicKey { 250 | cert, err := i.Certificate() 251 | if err != nil { 252 | return nil 253 | } 254 | 255 | return cert.PublicKey 256 | } 257 | 258 | // Sign implements the crypto.Signer interface. 259 | func (i *macIdentity) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { 260 | hash := opts.HashFunc() 261 | 262 | if len(digest) != hash.Size() { 263 | return nil, errors.New("bad digest for hash") 264 | } 265 | 266 | kref, err := i.getKeyRef() 267 | if err != nil { 268 | return nil, err 269 | } 270 | 271 | cdigest, err := bytesToCFData(digest) 272 | if err != nil { 273 | return nil, err 274 | } 275 | defer C.CFRelease(C.CFTypeRef(cdigest)) 276 | 277 | algo, err := i.getAlgo(hash) 278 | if err != nil { 279 | return nil, err 280 | } 281 | 282 | // sign the digest 283 | var cerr C.CFErrorRef 284 | csig := C.SecKeyCreateSignature(kref, algo, cdigest, &cerr) 285 | 286 | if err := cfErrorError(cerr); err != nil { 287 | defer C.CFRelease(C.CFTypeRef(cerr)) 288 | 289 | return nil, err 290 | } 291 | 292 | if csig == nilCFDataRef { 293 | return nil, errors.New("nil signature from SecKeyCreateSignature") 294 | } 295 | 296 | defer C.CFRelease(C.CFTypeRef(csig)) 297 | 298 | sig := cfDataToBytes(csig) 299 | 300 | return sig, nil 301 | } 302 | 303 | // getAlgo decides which algorithm to use with this key type for the given hash. 304 | func (i *macIdentity) getAlgo(hash crypto.Hash) (algo C.SecKeyAlgorithm, err error) { 305 | var crt *x509.Certificate 306 | if crt, err = i.Certificate(); err != nil { 307 | return 308 | } 309 | 310 | switch crt.PublicKey.(type) { 311 | case *ecdsa.PublicKey: 312 | switch hash { 313 | case crypto.SHA1: 314 | algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA1 315 | case crypto.SHA256: 316 | algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA256 317 | case crypto.SHA384: 318 | algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA384 319 | case crypto.SHA512: 320 | algo = C.kSecKeyAlgorithmECDSASignatureDigestX962SHA512 321 | default: 322 | err = ErrUnsupportedHash 323 | } 324 | case *rsa.PublicKey: 325 | switch hash { 326 | case crypto.SHA1: 327 | algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1 328 | case crypto.SHA256: 329 | algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256 330 | case crypto.SHA384: 331 | algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384 332 | case crypto.SHA512: 333 | algo = C.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512 334 | default: 335 | err = ErrUnsupportedHash 336 | } 337 | default: 338 | err = errors.New("unsupported key type") 339 | } 340 | 341 | return 342 | } 343 | 344 | // getKeyRef gets the SecKeyRef for this identity's pricate key. 345 | func (i *macIdentity) getKeyRef() (C.SecKeyRef, error) { 346 | if i.kref != nilSecKeyRef { 347 | return i.kref, nil 348 | } 349 | 350 | var keyRef C.SecKeyRef 351 | if err := osStatusError(C.SecIdentityCopyPrivateKey(i.ref, &keyRef)); err != nil { 352 | return nilSecKeyRef, err 353 | } 354 | 355 | i.kref = keyRef 356 | 357 | return i.kref, nil 358 | } 359 | 360 | // getCertRef gets the SecCertificateRef for this identity's certificate. 361 | func (i *macIdentity) getCertRef() (C.SecCertificateRef, error) { 362 | if i.cref != nilSecCertificateRef { 363 | return i.cref, nil 364 | } 365 | 366 | var certRef C.SecCertificateRef 367 | if err := osStatusError(C.SecIdentityCopyCertificate(i.ref, &certRef)); err != nil { 368 | return nilSecCertificateRef, err 369 | } 370 | 371 | i.cref = certRef 372 | 373 | return i.cref, nil 374 | } 375 | 376 | // exportCertRef gets a *x509.Certificate for the given SecCertificateRef. 377 | func exportCertRef(certRef C.SecCertificateRef) (*x509.Certificate, error) { 378 | derRef := C.SecCertificateCopyData(certRef) 379 | if derRef == nilCFDataRef { 380 | return nil, errors.New("error getting certificate from identity") 381 | } 382 | defer C.CFRelease(C.CFTypeRef(derRef)) 383 | 384 | der := cfDataToBytes(derRef) 385 | crt, err := x509.ParseCertificate(der) 386 | if err != nil { 387 | return nil, err 388 | } 389 | 390 | return crt, nil 391 | } 392 | 393 | // stringToCFString converts a Go string to a CFStringRef. 394 | func stringToCFString(gostr string) C.CFStringRef { 395 | cstr := C.CString(gostr) 396 | defer C.free(unsafe.Pointer(cstr)) 397 | 398 | return C.CFStringCreateWithCString(nilCFAllocatorRef, cstr, C.kCFStringEncodingUTF8) 399 | } 400 | 401 | // mapToCFDictionary converts a Go map[C.CFTypeRef]C.CFTypeRef to a 402 | // CFDictionaryRef. 403 | func mapToCFDictionary(gomap map[C.CFTypeRef]C.CFTypeRef) C.CFDictionaryRef { 404 | var ( 405 | n = len(gomap) 406 | keys = make([]unsafe.Pointer, 0, n) 407 | values = make([]unsafe.Pointer, 0, n) 408 | ) 409 | 410 | for k, v := range gomap { 411 | keys = append(keys, unsafe.Pointer(k)) 412 | values = append(values, unsafe.Pointer(v)) 413 | } 414 | 415 | return C.CFDictionaryCreate(nilCFAllocatorRef, &keys[0], &values[0], C.CFIndex(n), nil, nil) 416 | } 417 | 418 | // cfDataToBytes converts a CFDataRef to a Go byte slice. 419 | func cfDataToBytes(cfdata C.CFDataRef) []byte { 420 | nBytes := C.CFDataGetLength(cfdata) 421 | bytesPtr := C.CFDataGetBytePtr(cfdata) 422 | return C.GoBytes(unsafe.Pointer(bytesPtr), C.int(nBytes)) 423 | } 424 | 425 | // bytesToCFData converts a Go byte slice to a CFDataRef. 426 | func bytesToCFData(gobytes []byte) (C.CFDataRef, error) { 427 | var ( 428 | cptr = (*C.UInt8)(nil) 429 | clen = C.CFIndex(len(gobytes)) 430 | ) 431 | 432 | if len(gobytes) > 0 { 433 | cptr = (*C.UInt8)(&gobytes[0]) 434 | } 435 | 436 | cdata := C.CFDataCreate(nilCFAllocatorRef, cptr, clen) 437 | if cdata == nilCFDataRef { 438 | return nilCFDataRef, errors.New("error creatin cfdata") 439 | } 440 | 441 | return cdata, nil 442 | } 443 | 444 | // osStatus wraps a C.OSStatus 445 | type osStatus C.OSStatus 446 | 447 | const ( 448 | errSecItemNotFound = osStatus(C.errSecItemNotFound) 449 | ) 450 | 451 | // osStatusError returns an error for an OSStatus unless it is errSecSuccess. 452 | func osStatusError(s C.OSStatus) error { 453 | if s == C.errSecSuccess { 454 | return nil 455 | } 456 | 457 | return osStatus(s) 458 | } 459 | 460 | // Error implements the error interface. 461 | func (s osStatus) Error() string { 462 | return fmt.Sprintf("OSStatus %d", s) 463 | } 464 | 465 | // cfErrorError returns an error for a CFErrorRef unless it is nil. 466 | func cfErrorError(cerr C.CFErrorRef) error { 467 | if cerr == nilCFErrorRef { 468 | return nil 469 | } 470 | 471 | code := int(C.CFErrorGetCode(cerr)) 472 | 473 | if cdescription := C.CFErrorCopyDescription(cerr); cdescription != nilCFStringRef { 474 | defer C.CFRelease(C.CFTypeRef(cdescription)) 475 | 476 | if cstr := C.CFStringGetCStringPtr(cdescription, C.kCFStringEncodingUTF8); cstr != nil { 477 | str := C.GoString(cstr) 478 | 479 | return fmt.Errorf("CFError %d (%s)", code, str) 480 | } 481 | 482 | } 483 | 484 | return fmt.Errorf("CFError %d", code) 485 | } 486 | -------------------------------------------------------------------------------- /certstore_linux.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import "errors" 4 | 5 | // This will hopefully give a compiler error that will hint at the fact that 6 | // this package isn't designed to work on Linux. 7 | func init() { 8 | CERTSTORE_DOESNT_WORK_ON_LINIX 9 | } 10 | 11 | // Implement this function, just to silence other compiler errors. 12 | func openStore() (Store, error) { 13 | return nil, errors.New("certstore only works on macOS and Windows") 14 | } 15 | -------------------------------------------------------------------------------- /certstore_test.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/sha1" 9 | "crypto/sha256" 10 | "crypto/sha512" 11 | "crypto/x509" 12 | "testing" 13 | 14 | "github.com/github/fakeca" 15 | ) 16 | 17 | func TestImportDeleteRSA(t *testing.T) { 18 | ImportDeleteHelper(t, leafRSA) 19 | } 20 | 21 | func TestImportDeleteECDSA(t *testing.T) { 22 | ImportDeleteHelper(t, leafEC) 23 | } 24 | 25 | // ImportDeleteHelper is an abstraction for testing identity Import()/Delete(). 26 | func ImportDeleteHelper(t *testing.T, i *fakeca.Identity) { 27 | withStore(t, func(store Store) { 28 | // Import an identity 29 | if err := store.Import(i.PFX("asdf"), "asdf"); err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | // Look for our imported identity 34 | idents, err := store.Identities() 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | for _, ident := range idents { 39 | defer ident.Close() 40 | } 41 | 42 | var found Identity 43 | for _, ident := range idents { 44 | crt, errr := ident.Certificate() 45 | if errr != nil { 46 | t.Fatal(errr) 47 | } 48 | 49 | if i.Certificate.Equal(crt) { 50 | if found != nil { 51 | t.Fatal("duplicate identity imported") 52 | } 53 | 54 | found = ident 55 | } 56 | } 57 | if found == nil { 58 | t.Fatal("imported identity not found") 59 | } 60 | 61 | // Delete it 62 | if err = found.Delete(); err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | // Look for our deleted identity 67 | idents, err = store.Identities() 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | for _, ident := range idents { 72 | defer ident.Close() 73 | } 74 | 75 | found = nil 76 | for _, ident := range idents { 77 | crt, err := ident.Certificate() 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | if i.Certificate.Equal(crt) { 83 | found = ident 84 | } 85 | } 86 | if found != nil { 87 | t.Fatal("imported identity not deleted") 88 | } 89 | }) 90 | } 91 | 92 | func TestSignerRSA(t *testing.T) { 93 | rsaPriv, ok := leafRSA.PrivateKey.(*rsa.PrivateKey) 94 | if !ok { 95 | t.Fatal("expected priv to be an RSA private key") 96 | } 97 | 98 | withIdentity(t, leafRSA, func(ident Identity) { 99 | signer, err := ident.Signer() 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | pk := signer.Public() 105 | rsaPub, ok := pk.(*rsa.PublicKey) 106 | if !ok { 107 | t.Fatal("expected pk to be an RSA public key") 108 | } 109 | 110 | if rsaPub.E != rsaPriv.E { 111 | t.Fatalf("bad E. Got %d, expected %d", rsaPub.E, rsaPriv.E) 112 | } 113 | 114 | if rsaPub.N.Cmp(rsaPriv.N) != 0 { 115 | t.Fatalf("bad N. Got %s, expected %s", rsaPub.N.Text(16), rsaPriv.N.Text(16)) 116 | } 117 | 118 | // SHA1WithRSA 119 | sha1Digest := sha1.Sum([]byte("hello")) 120 | sig, err := signer.Sign(rand.Reader, sha1Digest[:], crypto.SHA1) 121 | if err != nil { 122 | // SHA1 should be supported by all platforms. 123 | t.Fatal(err) 124 | } 125 | if err = leafRSA.Certificate.CheckSignature(x509.SHA1WithRSA, []byte("hello"), sig); err != nil { 126 | t.Fatal(err) 127 | } 128 | 129 | // SHA256WithRSA 130 | sha256Digest := sha256.Sum256([]byte("hello")) 131 | sig, err = signer.Sign(rand.Reader, sha256Digest[:], crypto.SHA256) 132 | if err == ErrUnsupportedHash { 133 | // Some Windows CSPs may not support this algorithm. Pass... 134 | } else if err != nil { 135 | t.Fatal(err) 136 | } else { 137 | if err = leafRSA.Certificate.CheckSignature(x509.SHA256WithRSA, []byte("hello"), sig); err != nil { 138 | t.Fatal(err) 139 | } 140 | } 141 | 142 | // SHA384WithRSA 143 | sha384Digest := sha512.Sum384([]byte("hello")) 144 | sig, err = signer.Sign(rand.Reader, sha384Digest[:], crypto.SHA384) 145 | if err == ErrUnsupportedHash { 146 | // Some Windows CSPs may not support this algorithm. Pass... 147 | } else if err != nil { 148 | t.Fatal(err) 149 | } else { 150 | if err = leafRSA.Certificate.CheckSignature(x509.SHA384WithRSA, []byte("hello"), sig); err != nil { 151 | t.Fatal(err) 152 | } 153 | } 154 | 155 | // SHA512WithRSA 156 | sha512Digest := sha512.Sum512([]byte("hello")) 157 | sig, err = signer.Sign(rand.Reader, sha512Digest[:], crypto.SHA512) 158 | if err == ErrUnsupportedHash { 159 | // Some Windows CSPs may not support this algorithm. Pass... 160 | } else if err != nil { 161 | t.Fatal(err) 162 | } else { 163 | if err = leafRSA.Certificate.CheckSignature(x509.SHA512WithRSA, []byte("hello"), sig); err != nil { 164 | t.Fatal(err) 165 | } 166 | } 167 | 168 | // Bad digest size 169 | _, err = signer.Sign(rand.Reader, sha1Digest[5:], crypto.SHA1) 170 | if err == nil { 171 | t.Fatal("expected error for bad digest size") 172 | } 173 | 174 | // Unsupported hash 175 | sha224Digest := sha256.Sum224([]byte("hello")) 176 | _, err = signer.Sign(rand.Reader, sha224Digest[:], crypto.SHA224) 177 | if err != ErrUnsupportedHash { 178 | t.Fatal("expected ErrUnsupportedHash, got ", err) 179 | } 180 | }) 181 | } 182 | 183 | func TestSignerECDSA(t *testing.T) { 184 | ecPriv, ok := leafEC.PrivateKey.(*ecdsa.PrivateKey) 185 | if !ok { 186 | t.Fatal("expected priv to be an ECDSA private key") 187 | } 188 | 189 | withIdentity(t, leafEC, func(ident Identity) { 190 | signer, err := ident.Signer() 191 | if err != nil { 192 | t.Fatal(err) 193 | } 194 | 195 | pk := signer.Public() 196 | ecPub, ok := pk.(*ecdsa.PublicKey) 197 | if !ok { 198 | t.Fatal("expected pk to be an RSA public key") 199 | } 200 | 201 | if ecPub.X.Cmp(ecPriv.X) != 0 { 202 | t.Fatalf("bad X. Got %s, expected %s", ecPub.X.Text(16), ecPriv.X.Text(16)) 203 | } 204 | 205 | if ecPub.Y.Cmp(ecPriv.Y) != 0 { 206 | t.Fatalf("bad Y. Got %s, expected %s", ecPub.Y.Text(16), ecPriv.Y.Text(16)) 207 | } 208 | 209 | // ECDSAWithSHA1 210 | sha1Digest := sha1.Sum([]byte("hello")) 211 | sig, err := signer.Sign(rand.Reader, sha1Digest[:], crypto.SHA1) 212 | if err != nil { 213 | t.Fatal(err) 214 | } 215 | if err = leafEC.Certificate.CheckSignature(x509.ECDSAWithSHA1, []byte("hello"), sig); err != nil { 216 | t.Fatal(err) 217 | } 218 | 219 | // ECDSAWithSHA256 220 | sha256Digest := sha256.Sum256([]byte("hello")) 221 | sig, err = signer.Sign(rand.Reader, sha256Digest[:], crypto.SHA256) 222 | if err != nil { 223 | t.Fatal(err) 224 | } 225 | if err = leafEC.Certificate.CheckSignature(x509.ECDSAWithSHA256, []byte("hello"), sig); err != nil { 226 | t.Fatal(err) 227 | } 228 | 229 | // ECDSAWithSHA384 230 | sha384Digest := sha512.Sum384([]byte("hello")) 231 | sig, err = signer.Sign(rand.Reader, sha384Digest[:], crypto.SHA384) 232 | if err != nil { 233 | t.Fatal(err) 234 | } 235 | if err = leafEC.Certificate.CheckSignature(x509.ECDSAWithSHA384, []byte("hello"), sig); err != nil { 236 | t.Fatal(err) 237 | } 238 | 239 | // ECDSAWithSHA512 240 | sha512Digest := sha512.Sum512([]byte("hello")) 241 | sig, err = signer.Sign(rand.Reader, sha512Digest[:], crypto.SHA512) 242 | if err != nil { 243 | t.Fatal(err) 244 | } 245 | if err = leafEC.Certificate.CheckSignature(x509.ECDSAWithSHA512, []byte("hello"), sig); err != nil { 246 | t.Fatal(err) 247 | } 248 | 249 | // Bad digest size 250 | _, err = signer.Sign(rand.Reader, sha512Digest[5:], crypto.SHA512) 251 | if err == nil { 252 | t.Fatal("expected error for bad digest size") 253 | } 254 | }) 255 | } 256 | 257 | func TestCertificateRSA(t *testing.T) { 258 | CertificateHelper(t, leafRSA) 259 | } 260 | 261 | func TestCertificateEC(t *testing.T) { 262 | CertificateHelper(t, leafEC) 263 | } 264 | 265 | func CertificateHelper(t *testing.T, leaf *fakeca.Identity) { 266 | withIdentity(t, root, func(caIdent Identity) { 267 | withIdentity(t, intermediate, func(interIdent Identity) { 268 | withIdentity(t, leaf, func(leafIdent Identity) { 269 | crtActual, err := leafIdent.Certificate() 270 | if err != nil { 271 | t.Fatal(err) 272 | } 273 | if !leaf.Certificate.Equal(crtActual) { 274 | t.Fatal("Expected cert to match pfx") 275 | } 276 | 277 | chain, err := leafIdent.CertificateChain() 278 | if err != nil { 279 | t.Fatal(err) 280 | } 281 | if len(chain) != 3 { 282 | t.Fatalf("bad chain len. expected 3, got %d", len(chain)) 283 | } 284 | if !leaf.Certificate.Equal(chain[0]) { 285 | t.Fatal("first chain cert should be leaf") 286 | } 287 | if !intermediate.Certificate.Equal(chain[1]) { 288 | t.Fatal("second chain cert should be intermediate") 289 | } 290 | if !root.Certificate.Equal(chain[2]) { 291 | t.Fatal("second chain cert should be intermediate") 292 | } 293 | }) 294 | }) 295 | }) 296 | } 297 | -------------------------------------------------------------------------------- /certstore_windows.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | /* 4 | #cgo windows LDFLAGS: -lcrypt32 -lncrypt 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | char* errMsg(DWORD code) { 11 | char* lpMsgBuf; 12 | DWORD ret = 0; 13 | 14 | ret = FormatMessage( 15 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 16 | FORMAT_MESSAGE_FROM_SYSTEM | 17 | FORMAT_MESSAGE_IGNORE_INSERTS, 18 | NULL, 19 | code, 20 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 21 | (LPTSTR) &lpMsgBuf, 22 | 0, NULL); 23 | 24 | if (ret == 0) { 25 | return NULL; 26 | } else { 27 | return lpMsgBuf; 28 | } 29 | } 30 | */ 31 | import "C" 32 | 33 | import ( 34 | "crypto" 35 | "crypto/ecdsa" 36 | "crypto/rsa" 37 | "crypto/x509" 38 | "encoding/asn1" 39 | "fmt" 40 | "io" 41 | "math/big" 42 | "unicode/utf16" 43 | "unsafe" 44 | 45 | "github.com/pkg/errors" 46 | ) 47 | 48 | const ( 49 | winTrue C.WINBOOL = 1 50 | winFalse C.WINBOOL = 0 51 | 52 | // ERROR_SUCCESS 53 | ERROR_SUCCESS = 0x00000000 54 | 55 | // CRYPT_E_NOT_FOUND — Cannot find object or property. 56 | CRYPT_E_NOT_FOUND = 0x80092004 57 | 58 | // NTE_BAD_ALGID — Invalid algorithm specified. 59 | NTE_BAD_ALGID = 0x80090008 60 | ) 61 | 62 | // winAPIFlag specifies the flags that should be passed to 63 | // CryptAcquireCertificatePrivateKey. This impacts whether the CryptoAPI or CNG 64 | // API will be used. 65 | // 66 | // Possible values are: 67 | // 0x00000000 — — Only use CryptoAPI. 68 | // 0x00010000 — CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG — Prefer CryptoAPI. 69 | // 0x00020000 — CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG — Prefer CNG. 70 | // 0x00040000 — CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG — Only uyse CNG. 71 | var winAPIFlag C.DWORD = C.CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG 72 | 73 | // winStore is a wrapper around a C.HCERTSTORE. 74 | type winStore struct { 75 | store C.HCERTSTORE 76 | } 77 | 78 | // openStore opens the current user's personal cert store. 79 | func openStore() (*winStore, error) { 80 | storeName := unsafe.Pointer(stringToUTF16("MY")) 81 | defer C.free(storeName) 82 | 83 | store := C.CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0, C.CERT_SYSTEM_STORE_CURRENT_USER, storeName) 84 | if store == nil { 85 | return nil, lastError("failed to open system cert store") 86 | } 87 | 88 | return &winStore{store}, nil 89 | } 90 | 91 | // Identities implements the Store interface. 92 | func (s *winStore) Identities() ([]Identity, error) { 93 | var ( 94 | err error 95 | idents = []Identity{} 96 | 97 | // CertFindChainInStore parameters 98 | encoding = C.DWORD(C.X509_ASN_ENCODING) 99 | flags = C.DWORD(C.CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG | C.CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG) 100 | findType = C.DWORD(C.CERT_CHAIN_FIND_BY_ISSUER) 101 | params = &C.CERT_CHAIN_FIND_BY_ISSUER_PARA{cbSize: C.DWORD(unsafe.Sizeof(C.CERT_CHAIN_FIND_BY_ISSUER_PARA{}))} 102 | paramsPtr = unsafe.Pointer(params) 103 | chainCtx = C.PCCERT_CHAIN_CONTEXT(nil) 104 | ) 105 | 106 | for { 107 | if chainCtx = C.CertFindChainInStore(s.store, encoding, flags, findType, paramsPtr, chainCtx); chainCtx == nil { 108 | break 109 | } 110 | if chainCtx.cChain < 1 { 111 | err = errors.New("bad chain") 112 | goto fail 113 | } 114 | 115 | // not sure why this isn't 1 << 29 116 | const maxPointerArray = 1 << 28 117 | 118 | // rgpChain is actually an array, but we only care about the first one. 119 | simpleChain := *chainCtx.rgpChain 120 | if simpleChain.cElement < 1 || simpleChain.cElement > maxPointerArray { 121 | err = errors.New("bad chain") 122 | goto fail 123 | } 124 | 125 | // Hacky way to get chain elements (c array) as a slice. 126 | chainElts := (*[maxPointerArray]C.PCERT_CHAIN_ELEMENT)(unsafe.Pointer(simpleChain.rgpElement))[:simpleChain.cElement:simpleChain.cElement] 127 | 128 | // Build chain of certificates from each elt's certificate context. 129 | chain := make([]C.PCCERT_CONTEXT, len(chainElts)) 130 | for j := range chainElts { 131 | chain[j] = chainElts[j].pCertContext 132 | } 133 | 134 | idents = append(idents, newWinIdentity(chain)) 135 | } 136 | 137 | if err = checkError("failed to iterate certs in store"); err != nil && errors.Cause(err) != errCode(CRYPT_E_NOT_FOUND) { 138 | goto fail 139 | } 140 | 141 | return idents, nil 142 | 143 | fail: 144 | for _, ident := range idents { 145 | ident.Close() 146 | } 147 | 148 | return nil, err 149 | } 150 | 151 | // Import implements the Store interface. 152 | func (s *winStore) Import(data []byte, password string) error { 153 | cdata := C.CBytes(data) 154 | defer C.free(cdata) 155 | 156 | cpw := stringToUTF16(password) 157 | defer C.free(unsafe.Pointer(cpw)) 158 | 159 | pfx := &C.CRYPT_DATA_BLOB{ 160 | cbData: C.DWORD(len(data)), 161 | pbData: (*C.BYTE)(cdata), 162 | } 163 | 164 | flags := C.CRYPT_USER_KEYSET 165 | 166 | // import into preferred KSP 167 | if winAPIFlag&C.CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG > 0 { 168 | flags |= C.PKCS12_PREFER_CNG_KSP 169 | } else if winAPIFlag&C.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG > 0 { 170 | flags |= C.PKCS12_ALWAYS_CNG_KSP 171 | } 172 | 173 | store := C.PFXImportCertStore(pfx, cpw, C.DWORD(flags)) 174 | if store == nil { 175 | return lastError("failed to import PFX cert store") 176 | } 177 | defer C.CertCloseStore(store, C.CERT_CLOSE_STORE_FORCE_FLAG) 178 | 179 | var ( 180 | ctx = C.PCCERT_CONTEXT(nil) 181 | encoding = C.DWORD(C.X509_ASN_ENCODING | C.PKCS_7_ASN_ENCODING) 182 | ) 183 | 184 | for { 185 | // iterate through certs in temporary store 186 | if ctx = C.CertFindCertificateInStore(store, encoding, 0, C.CERT_FIND_ANY, nil, ctx); ctx == nil { 187 | if err := checkError("failed to iterate certs in store"); err != nil && errors.Cause(err) != errCode(CRYPT_E_NOT_FOUND) { 188 | return err 189 | } 190 | 191 | break 192 | } 193 | 194 | // Copy the cert to the system store. 195 | if ok := C.CertAddCertificateContextToStore(s.store, ctx, C.CERT_STORE_ADD_REPLACE_EXISTING, nil); ok == winFalse { 196 | return lastError("failed to add importerd certificate to MY store") 197 | } 198 | } 199 | 200 | return nil 201 | } 202 | 203 | // Close implements the Store interface. 204 | func (s *winStore) Close() { 205 | C.CertCloseStore(s.store, 0) 206 | s.store = nil 207 | } 208 | 209 | // winIdentity implements the Identity interface. 210 | type winIdentity struct { 211 | chain []C.PCCERT_CONTEXT 212 | signer *winPrivateKey 213 | } 214 | 215 | func newWinIdentity(chain []C.PCCERT_CONTEXT) *winIdentity { 216 | for _, ctx := range chain { 217 | C.CertDuplicateCertificateContext(ctx) 218 | } 219 | 220 | return &winIdentity{chain: chain} 221 | } 222 | 223 | // Certificate implements the Identity interface. 224 | func (i *winIdentity) Certificate() (*x509.Certificate, error) { 225 | return exportCertCtx(i.chain[0]) 226 | } 227 | 228 | // CertificateChain implements the Identity interface. 229 | func (i *winIdentity) CertificateChain() ([]*x509.Certificate, error) { 230 | var ( 231 | certs = make([]*x509.Certificate, len(i.chain)) 232 | err error 233 | ) 234 | 235 | for j := range i.chain { 236 | if certs[j], err = exportCertCtx(i.chain[j]); err != nil { 237 | return nil, err 238 | } 239 | } 240 | 241 | return certs, nil 242 | } 243 | 244 | // Signer implements the Identity interface. 245 | func (i *winIdentity) Signer() (crypto.Signer, error) { 246 | return i.getPrivateKey() 247 | } 248 | 249 | // getPrivateKey gets this identity's private *winPrivateKey. 250 | func (i *winIdentity) getPrivateKey() (*winPrivateKey, error) { 251 | if i.signer != nil { 252 | return i.signer, nil 253 | } 254 | 255 | cert, err := i.Certificate() 256 | if err != nil { 257 | return nil, errors.Wrap(err, "failed to get identity certificate") 258 | } 259 | 260 | signer, err := newWinPrivateKey(i.chain[0], cert.PublicKey) 261 | if err != nil { 262 | return nil, errors.Wrap(err, "failed to load identity private key") 263 | } 264 | 265 | i.signer = signer 266 | 267 | return i.signer, nil 268 | } 269 | 270 | // Delete implements the Identity interface. 271 | func (i *winIdentity) Delete() error { 272 | // duplicate cert context, since CertDeleteCertificateFromStore will free it. 273 | deleteCtx := C.CertDuplicateCertificateContext(i.chain[0]) 274 | 275 | // try deleting cert 276 | if ok := C.CertDeleteCertificateFromStore(deleteCtx); ok == winFalse { 277 | return lastError("failed to delete certificate from store") 278 | } 279 | 280 | // try deleting private key 281 | wpk, err := i.getPrivateKey() 282 | if err != nil { 283 | return errors.Wrap(err, "failed to get identity private key") 284 | } 285 | 286 | if err := wpk.Delete(); err != nil { 287 | return errors.Wrap(err, "failed to delete identity private key") 288 | } 289 | 290 | return nil 291 | } 292 | 293 | // Close implements the Identity interface. 294 | func (i *winIdentity) Close() { 295 | if i.signer != nil { 296 | i.signer.Close() 297 | i.signer = nil 298 | } 299 | 300 | for _, ctx := range i.chain { 301 | C.CertFreeCertificateContext(ctx) 302 | i.chain = nil 303 | } 304 | } 305 | 306 | // winPrivateKey is a wrapper around a HCRYPTPROV_OR_NCRYPT_KEY_HANDLE. 307 | type winPrivateKey struct { 308 | publicKey crypto.PublicKey 309 | 310 | // CryptoAPI fields 311 | capiProv C.HCRYPTPROV 312 | 313 | // CNG fields 314 | cngHandle C.NCRYPT_KEY_HANDLE 315 | keySpec C.DWORD 316 | } 317 | 318 | // newWinPrivateKey gets a *winPrivateKey for the given certificate. 319 | func newWinPrivateKey(certCtx C.PCCERT_CONTEXT, publicKey crypto.PublicKey) (*winPrivateKey, error) { 320 | var ( 321 | provOrKey C.HCRYPTPROV_OR_NCRYPT_KEY_HANDLE 322 | keySpec C.DWORD 323 | mustFree C.WINBOOL 324 | ) 325 | 326 | if publicKey == nil { 327 | return nil, errors.New("nil public key") 328 | } 329 | 330 | // Get a handle for the found private key. 331 | if ok := C.CryptAcquireCertificatePrivateKey(certCtx, winAPIFlag, nil, &provOrKey, &keySpec, &mustFree); ok == winFalse { 332 | return nil, lastError("failed to get private key for certificate") 333 | } 334 | 335 | if mustFree != winTrue { 336 | // This shouldn't happen since we're not asking for cached keys. 337 | return nil, errors.New("CryptAcquireCertificatePrivateKey set mustFree") 338 | } 339 | 340 | if keySpec == C.CERT_NCRYPT_KEY_SPEC { 341 | return &winPrivateKey{ 342 | publicKey: publicKey, 343 | cngHandle: C.NCRYPT_KEY_HANDLE(provOrKey), 344 | }, nil 345 | } else { 346 | return &winPrivateKey{ 347 | publicKey: publicKey, 348 | capiProv: C.HCRYPTPROV(provOrKey), 349 | keySpec: keySpec, 350 | }, nil 351 | } 352 | } 353 | 354 | // Public implements the crypto.Signer interface. 355 | func (wpk *winPrivateKey) Public() crypto.PublicKey { 356 | return wpk.publicKey 357 | } 358 | 359 | // Sign implements the crypto.Signer interface. 360 | func (wpk *winPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { 361 | if wpk.capiProv != 0 { 362 | return wpk.capiSignHash(opts.HashFunc(), digest) 363 | } else if wpk.cngHandle != 0 { 364 | return wpk.cngSignHash(opts.HashFunc(), digest) 365 | } else { 366 | return nil, errors.New("bad private key") 367 | } 368 | } 369 | 370 | // cngSignHash signs a digest using the CNG APIs. 371 | func (wpk *winPrivateKey) cngSignHash(hash crypto.Hash, digest []byte) ([]byte, error) { 372 | if len(digest) != hash.Size() { 373 | return nil, errors.New("bad digest for hash") 374 | } 375 | 376 | var ( 377 | // input 378 | padPtr = unsafe.Pointer(nil) 379 | digestPtr = (*C.BYTE)(&digest[0]) 380 | digestLen = C.DWORD(len(digest)) 381 | flags = C.DWORD(0) 382 | 383 | // output 384 | sigLen = C.DWORD(0) 385 | ) 386 | 387 | // setup pkcs1v1.5 padding for RSA 388 | if _, isRSA := wpk.publicKey.(*rsa.PublicKey); isRSA { 389 | flags |= C.BCRYPT_PAD_PKCS1 390 | padInfo := C.BCRYPT_PKCS1_PADDING_INFO{} 391 | padPtr = unsafe.Pointer(&padInfo) 392 | 393 | switch hash { 394 | case crypto.SHA1: 395 | padInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM 396 | case crypto.SHA256: 397 | padInfo.pszAlgId = BCRYPT_SHA256_ALGORITHM 398 | case crypto.SHA384: 399 | padInfo.pszAlgId = BCRYPT_SHA384_ALGORITHM 400 | case crypto.SHA512: 401 | padInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM 402 | default: 403 | return nil, ErrUnsupportedHash 404 | } 405 | } 406 | 407 | // get signature length 408 | if err := checkStatus(C.NCryptSignHash(wpk.cngHandle, padPtr, digestPtr, digestLen, nil, 0, &sigLen, flags)); err != nil { 409 | return nil, errors.Wrap(err, "failed to get signature length") 410 | } 411 | 412 | // get signature 413 | sig := make([]byte, sigLen) 414 | sigPtr := (*C.BYTE)(&sig[0]) 415 | if err := checkStatus(C.NCryptSignHash(wpk.cngHandle, padPtr, digestPtr, digestLen, sigPtr, sigLen, &sigLen, flags)); err != nil { 416 | return nil, errors.Wrap(err, "failed to sign digest") 417 | } 418 | 419 | // CNG returns a raw ECDSA signature, but we wan't ASN.1 DER encoding. 420 | if _, isEC := wpk.publicKey.(*ecdsa.PublicKey); isEC { 421 | if len(sig)%2 != 0 { 422 | return nil, errors.New("bad ecdsa signature from CNG") 423 | } 424 | 425 | type ecdsaSignature struct { 426 | R, S *big.Int 427 | } 428 | 429 | r := new(big.Int).SetBytes(sig[:len(sig)/2]) 430 | s := new(big.Int).SetBytes(sig[len(sig)/2:]) 431 | 432 | encoded, err := asn1.Marshal(ecdsaSignature{r, s}) 433 | if err != nil { 434 | return nil, errors.Wrap(err, "failed to ASN.1 encode EC signature") 435 | } 436 | 437 | return encoded, nil 438 | } 439 | 440 | return sig, nil 441 | } 442 | 443 | // capiSignHash signs a digest using the CryptoAPI APIs. 444 | func (wpk *winPrivateKey) capiSignHash(hash crypto.Hash, digest []byte) ([]byte, error) { 445 | if len(digest) != hash.Size() { 446 | return nil, errors.New("bad digest for hash") 447 | } 448 | 449 | // Figure out which CryptoAPI hash algorithm we're using. 450 | var hash_alg C.ALG_ID 451 | 452 | switch hash { 453 | case crypto.SHA1: 454 | hash_alg = C.CALG_SHA1 455 | case crypto.SHA256: 456 | hash_alg = C.CALG_SHA_256 457 | case crypto.SHA384: 458 | hash_alg = C.CALG_SHA_384 459 | case crypto.SHA512: 460 | hash_alg = C.CALG_SHA_512 461 | default: 462 | return nil, ErrUnsupportedHash 463 | } 464 | 465 | // Instantiate a CryptoAPI hash object. 466 | var chash C.HCRYPTHASH 467 | 468 | if ok := C.CryptCreateHash(C.HCRYPTPROV(wpk.capiProv), hash_alg, 0, 0, &chash); ok == winFalse { 469 | if err := lastError("failed to create hash"); errors.Cause(err) == errCode(NTE_BAD_ALGID) { 470 | return nil, ErrUnsupportedHash 471 | } else { 472 | return nil, err 473 | } 474 | } 475 | defer C.CryptDestroyHash(chash) 476 | 477 | // Make sure the hash size matches. 478 | var ( 479 | hashSize C.DWORD 480 | hashSizePtr = (*C.BYTE)(unsafe.Pointer(&hashSize)) 481 | hashSizeLen = C.DWORD(unsafe.Sizeof(hashSize)) 482 | ) 483 | 484 | if ok := C.CryptGetHashParam(chash, C.HP_HASHSIZE, hashSizePtr, &hashSizeLen, 0); ok == winFalse { 485 | return nil, lastError("failed to get hash size") 486 | } 487 | 488 | if hash.Size() != int(hashSize) { 489 | return nil, errors.New("invalid CryptoAPI hash") 490 | } 491 | 492 | // Put our digest into the hash object. 493 | digestPtr := (*C.BYTE)(unsafe.Pointer(&digest[0])) 494 | if ok := C.CryptSetHashParam(chash, C.HP_HASHVAL, digestPtr, 0); ok == winFalse { 495 | return nil, lastError("failed to set hash digest") 496 | } 497 | 498 | // Get signature length. 499 | var sigLen C.DWORD 500 | 501 | if ok := C.CryptSignHash(chash, wpk.keySpec, nil, 0, nil, &sigLen); ok == winFalse { 502 | return nil, lastError("failed to get signature length") 503 | } 504 | 505 | // Get signature 506 | var ( 507 | sig = make([]byte, int(sigLen)) 508 | sigPtr = (*C.BYTE)(unsafe.Pointer(&sig[0])) 509 | ) 510 | 511 | if ok := C.CryptSignHash(chash, wpk.keySpec, nil, 0, sigPtr, &sigLen); ok == winFalse { 512 | return nil, lastError("failed to sign digest") 513 | } 514 | 515 | // Signature is little endian, but we want big endian. Reverse it. 516 | for i := len(sig)/2 - 1; i >= 0; i-- { 517 | opp := len(sig) - 1 - i 518 | sig[i], sig[opp] = sig[opp], sig[i] 519 | } 520 | 521 | return sig, nil 522 | } 523 | 524 | func (wpk *winPrivateKey) Delete() error { 525 | if wpk.cngHandle != 0 { 526 | // Delete CNG key 527 | if err := checkStatus(C.NCryptDeleteKey(wpk.cngHandle, 0)); err != nil { 528 | return err 529 | } 530 | } else if wpk.capiProv != 0 { 531 | // Delete CryptoAPI key 532 | var ( 533 | param unsafe.Pointer 534 | err error 535 | 536 | containerName C.LPCTSTR 537 | providerName C.LPCTSTR 538 | providerType *C.DWORD 539 | ) 540 | 541 | if param, err = wpk.getProviderParam(C.PP_CONTAINER); err != nil { 542 | return errors.Wrap(err, "failed to get PP_CONTAINER") 543 | } else { 544 | containerName = C.LPCTSTR(param) 545 | } 546 | 547 | if param, err = wpk.getProviderParam(C.PP_NAME); err != nil { 548 | return errors.Wrap(err, "failed to get PP_NAME") 549 | } else { 550 | providerName = C.LPCTSTR(param) 551 | } 552 | 553 | if param, err = wpk.getProviderParam(C.PP_PROVTYPE); err != nil { 554 | return errors.Wrap(err, "failed to get PP_PROVTYPE") 555 | } else { 556 | providerType = (*C.DWORD)(param) 557 | } 558 | 559 | // use CRYPT_SILENT too? 560 | var prov C.HCRYPTPROV 561 | if ok := C.CryptAcquireContext(&prov, containerName, providerName, *providerType, C.CRYPT_DELETEKEYSET); ok == winFalse { 562 | return lastError("failed to delete key set") 563 | } 564 | } else { 565 | return errors.New("bad private key") 566 | } 567 | 568 | return nil 569 | } 570 | 571 | // getProviderParam gets a parameter about a provider. 572 | func (wpk *winPrivateKey) getProviderParam(param C.DWORD) (unsafe.Pointer, error) { 573 | var dataLen C.DWORD 574 | if ok := C.CryptGetProvParam(wpk.capiProv, param, nil, &dataLen, 0); ok == winFalse { 575 | return nil, lastError("failed to get provider parameter size") 576 | } 577 | 578 | data := make([]byte, dataLen) 579 | dataPtr := (*C.BYTE)(unsafe.Pointer(&data[0])) 580 | if ok := C.CryptGetProvParam(wpk.capiProv, param, dataPtr, &dataLen, 0); ok == winFalse { 581 | return nil, lastError("failed to get provider parameter") 582 | } 583 | 584 | // TODO leaking memory here 585 | return C.CBytes(data), nil 586 | } 587 | 588 | // Close closes this winPrivateKey. 589 | func (wpk *winPrivateKey) Close() { 590 | if wpk.cngHandle != 0 { 591 | C.NCryptFreeObject(C.NCRYPT_HANDLE(wpk.cngHandle)) 592 | wpk.cngHandle = 0 593 | } 594 | 595 | if wpk.capiProv != 0 { 596 | C.CryptReleaseContext(wpk.capiProv, 0) 597 | wpk.capiProv = 0 598 | } 599 | } 600 | 601 | // exportCertCtx exports a PCCERT_CONTEXT as an *x509.Certificate. 602 | func exportCertCtx(ctx C.PCCERT_CONTEXT) (*x509.Certificate, error) { 603 | der := C.GoBytes(unsafe.Pointer(ctx.pbCertEncoded), C.int(ctx.cbCertEncoded)) 604 | 605 | cert, err := x509.ParseCertificate(der) 606 | if err != nil { 607 | return nil, errors.Wrap(err, "certificate parsing failed") 608 | } 609 | 610 | return cert, nil 611 | } 612 | 613 | type errCode uint64 614 | 615 | // lastError gets the last error from the current thread. If there isn't one, it 616 | // returns a new error. 617 | func lastError(msg string) error { 618 | if err := checkError(msg); err != nil { 619 | return err 620 | } 621 | 622 | return errors.New(msg) 623 | } 624 | 625 | // checkError tries to get the last error from the current thread. If there 626 | // isn't one, it returns nil. 627 | func checkError(msg string) error { 628 | if code := errCode(C.GetLastError()); code != 0 { 629 | return errors.Wrap(code, msg) 630 | } 631 | 632 | return nil 633 | } 634 | 635 | func (c errCode) Error() string { 636 | cmsg := C.errMsg(C.DWORD(c)) 637 | if cmsg == nil { 638 | return fmt.Sprintf("Error %X", int(c)) 639 | } 640 | defer C.LocalFree(C.HLOCAL(cmsg)) 641 | 642 | gomsg := C.GoString(cmsg) 643 | 644 | return fmt.Sprintf("Error: %X %s", int(c), gomsg) 645 | } 646 | 647 | type securityStatus uint64 648 | 649 | func checkStatus(s C.SECURITY_STATUS) error { 650 | ss := securityStatus(s) 651 | 652 | if ss == ERROR_SUCCESS { 653 | return nil 654 | } 655 | 656 | if ss == NTE_BAD_ALGID { 657 | return ErrUnsupportedHash 658 | } 659 | 660 | return ss 661 | } 662 | 663 | func (ss securityStatus) Error() string { 664 | return fmt.Sprintf("SECURITY_STATUS %d", int(ss)) 665 | } 666 | 667 | func stringToUTF16(s string) C.LPCWSTR { 668 | // Not sure why this isn't 1 << 30... 669 | const maxUint16Array = 1 << 29 670 | 671 | if len(s) > maxUint16Array { 672 | panic("string too long") 673 | } 674 | 675 | wstr := utf16.Encode([]rune(s)) 676 | 677 | p := C.calloc(C.size_t(len(wstr)+1), C.size_t(unsafe.Sizeof(uint16(0)))) 678 | pp := (*[maxUint16Array]uint16)(p) 679 | copy(pp[:], wstr) 680 | 681 | return (C.LPCWSTR)(p) 682 | } 683 | -------------------------------------------------------------------------------- /crypt_strings_windows.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | 8 | // 9 | // Go complains about LPCWSTR constants not being defined, so we define getter 10 | // methods for a bunch of constants we might want. 11 | // 12 | 13 | // Store name 14 | LPCSTR GET_CERT_STORE_PROV_SYSTEM_W() { return CERT_STORE_PROV_SYSTEM_W; } 15 | 16 | // NCRYPT Object Property Names 17 | LPCWSTR GET_NCRYPT_ALGORITHM_GROUP_PROPERTY() { return NCRYPT_ALGORITHM_GROUP_PROPERTY; } 18 | LPCWSTR GET_NCRYPT_ALGORITHM_PROPERTY() { return NCRYPT_ALGORITHM_PROPERTY; } 19 | LPCWSTR GET_NCRYPT_BLOCK_LENGTH_PROPERTY() { return NCRYPT_BLOCK_LENGTH_PROPERTY; } 20 | LPCWSTR GET_NCRYPT_CERTIFICATE_PROPERTY() { return NCRYPT_CERTIFICATE_PROPERTY; } 21 | LPCWSTR GET_NCRYPT_DH_PARAMETERS_PROPERTY() { return NCRYPT_DH_PARAMETERS_PROPERTY; } 22 | LPCWSTR GET_NCRYPT_EXPORT_POLICY_PROPERTY() { return NCRYPT_EXPORT_POLICY_PROPERTY; } 23 | LPCWSTR GET_NCRYPT_IMPL_TYPE_PROPERTY() { return NCRYPT_IMPL_TYPE_PROPERTY; } 24 | LPCWSTR GET_NCRYPT_KEY_TYPE_PROPERTY() { return NCRYPT_KEY_TYPE_PROPERTY; } 25 | LPCWSTR GET_NCRYPT_KEY_USAGE_PROPERTY() { return NCRYPT_KEY_USAGE_PROPERTY; } 26 | LPCWSTR GET_NCRYPT_LAST_MODIFIED_PROPERTY() { return NCRYPT_LAST_MODIFIED_PROPERTY; } 27 | LPCWSTR GET_NCRYPT_LENGTH_PROPERTY() { return NCRYPT_LENGTH_PROPERTY; } 28 | LPCWSTR GET_NCRYPT_LENGTHS_PROPERTY() { return NCRYPT_LENGTHS_PROPERTY; } 29 | LPCWSTR GET_NCRYPT_MAX_NAME_LENGTH_PROPERTY() { return NCRYPT_MAX_NAME_LENGTH_PROPERTY; } 30 | LPCWSTR GET_NCRYPT_NAME_PROPERTY() { return NCRYPT_NAME_PROPERTY; } 31 | LPCWSTR GET_NCRYPT_PIN_PROMPT_PROPERTY() { return NCRYPT_PIN_PROMPT_PROPERTY; } 32 | LPCWSTR GET_NCRYPT_PIN_PROPERTY() { return NCRYPT_PIN_PROPERTY; } 33 | LPCWSTR GET_NCRYPT_PROVIDER_HANDLE_PROPERTY() { return NCRYPT_PROVIDER_HANDLE_PROPERTY; } 34 | LPCWSTR GET_NCRYPT_READER_PROPERTY() { return NCRYPT_READER_PROPERTY; } 35 | LPCWSTR GET_NCRYPT_ROOT_CERTSTORE_PROPERTY() { return NCRYPT_ROOT_CERTSTORE_PROPERTY; } 36 | LPCWSTR GET_NCRYPT_SECURE_PIN_PROPERTY() { return NCRYPT_SECURE_PIN_PROPERTY; } 37 | LPCWSTR GET_NCRYPT_SECURITY_DESCR_PROPERTY() { return NCRYPT_SECURITY_DESCR_PROPERTY; } 38 | LPCWSTR GET_NCRYPT_SECURITY_DESCR_SUPPORT_PROPERTY() { return NCRYPT_SECURITY_DESCR_SUPPORT_PROPERTY; } 39 | LPCWSTR GET_NCRYPT_SMARTCARD_GUID_PROPERTY() { return NCRYPT_SMARTCARD_GUID_PROPERTY; } 40 | LPCWSTR GET_NCRYPT_UI_POLICY_PROPERTY() { return NCRYPT_UI_POLICY_PROPERTY; } 41 | LPCWSTR GET_NCRYPT_UNIQUE_NAME_PROPERTY() { return NCRYPT_UNIQUE_NAME_PROPERTY; } 42 | LPCWSTR GET_NCRYPT_USE_CONTEXT_PROPERTY() { return NCRYPT_USE_CONTEXT_PROPERTY; } 43 | LPCWSTR GET_NCRYPT_USE_COUNT_ENABLED_PROPERTY() { return NCRYPT_USE_COUNT_ENABLED_PROPERTY; } 44 | LPCWSTR GET_NCRYPT_USE_COUNT_PROPERTY() { return NCRYPT_USE_COUNT_PROPERTY; } 45 | LPCWSTR GET_NCRYPT_USER_CERTSTORE_PROPERTY() { return NCRYPT_USER_CERTSTORE_PROPERTY; } 46 | LPCWSTR GET_NCRYPT_VERSION_PROPERTY() { return NCRYPT_VERSION_PROPERTY; } 47 | LPCWSTR GET_NCRYPT_WINDOW_HANDLE_PROPERTY() { return NCRYPT_WINDOW_HANDLE_PROPERTY; } 48 | 49 | // BCRYPT BLOB Types 50 | LPCWSTR GET_BCRYPT_DH_PRIVATE_BLOB() { return BCRYPT_DH_PRIVATE_BLOB; } 51 | LPCWSTR GET_BCRYPT_DH_PUBLIC_BLOB() { return BCRYPT_DH_PUBLIC_BLOB; } 52 | LPCWSTR GET_BCRYPT_DSA_PRIVATE_BLOB() { return BCRYPT_DSA_PRIVATE_BLOB; } 53 | LPCWSTR GET_BCRYPT_DSA_PUBLIC_BLOB() { return BCRYPT_DSA_PUBLIC_BLOB; } 54 | LPCWSTR GET_BCRYPT_ECCPRIVATE_BLOB() { return BCRYPT_ECCPRIVATE_BLOB; } 55 | LPCWSTR GET_BCRYPT_ECCPUBLIC_BLOB() { return BCRYPT_ECCPUBLIC_BLOB; } 56 | LPCWSTR GET_BCRYPT_PUBLIC_KEY_BLOB() { return BCRYPT_PUBLIC_KEY_BLOB; } 57 | LPCWSTR GET_BCRYPT_PRIVATE_KEY_BLOB() { return BCRYPT_PRIVATE_KEY_BLOB; } 58 | LPCWSTR GET_BCRYPT_RSAFULLPRIVATE_BLOB() { return BCRYPT_RSAFULLPRIVATE_BLOB; } 59 | LPCWSTR GET_BCRYPT_RSAPRIVATE_BLOB() { return BCRYPT_RSAPRIVATE_BLOB; } 60 | LPCWSTR GET_BCRYPT_RSAPUBLIC_BLOB() { return BCRYPT_RSAPUBLIC_BLOB; } 61 | 62 | // BCRYPT Algorithm Names 63 | LPCWSTR GET_BCRYPT_3DES_ALGORITHM() { return BCRYPT_3DES_ALGORITHM; } 64 | LPCWSTR GET_BCRYPT_3DES_112_ALGORITHM() { return BCRYPT_3DES_112_ALGORITHM; } 65 | LPCWSTR GET_BCRYPT_AES_ALGORITHM() { return BCRYPT_AES_ALGORITHM; } 66 | LPCWSTR GET_BCRYPT_AES_CMAC_ALGORITHM() { return BCRYPT_AES_CMAC_ALGORITHM; } 67 | LPCWSTR GET_BCRYPT_AES_GMAC_ALGORITHM() { return BCRYPT_AES_GMAC_ALGORITHM; } 68 | LPCWSTR GET_BCRYPT_CAPI_KDF_ALGORITHM() { return BCRYPT_CAPI_KDF_ALGORITHM; } 69 | LPCWSTR GET_BCRYPT_DES_ALGORITHM() { return BCRYPT_DES_ALGORITHM; } 70 | LPCWSTR GET_BCRYPT_DESX_ALGORITHM() { return BCRYPT_DESX_ALGORITHM; } 71 | LPCWSTR GET_BCRYPT_DH_ALGORITHM() { return BCRYPT_DH_ALGORITHM; } 72 | LPCWSTR GET_BCRYPT_DSA_ALGORITHM() { return BCRYPT_DSA_ALGORITHM; } 73 | LPCWSTR GET_BCRYPT_ECDH_P256_ALGORITHM() { return BCRYPT_ECDH_P256_ALGORITHM; } 74 | LPCWSTR GET_BCRYPT_ECDH_P384_ALGORITHM() { return BCRYPT_ECDH_P384_ALGORITHM; } 75 | LPCWSTR GET_BCRYPT_ECDH_P521_ALGORITHM() { return BCRYPT_ECDH_P521_ALGORITHM; } 76 | LPCWSTR GET_BCRYPT_ECDSA_P256_ALGORITHM() { return BCRYPT_ECDSA_P256_ALGORITHM; } 77 | LPCWSTR GET_BCRYPT_ECDSA_P384_ALGORITHM() { return BCRYPT_ECDSA_P384_ALGORITHM; } 78 | LPCWSTR GET_BCRYPT_ECDSA_P521_ALGORITHM() { return BCRYPT_ECDSA_P521_ALGORITHM; } 79 | LPCWSTR GET_BCRYPT_MD2_ALGORITHM() { return BCRYPT_MD2_ALGORITHM; } 80 | LPCWSTR GET_BCRYPT_MD4_ALGORITHM() { return BCRYPT_MD4_ALGORITHM; } 81 | LPCWSTR GET_BCRYPT_MD5_ALGORITHM() { return BCRYPT_MD5_ALGORITHM; } 82 | LPCWSTR GET_BCRYPT_RC2_ALGORITHM() { return BCRYPT_RC2_ALGORITHM; } 83 | LPCWSTR GET_BCRYPT_RC4_ALGORITHM() { return BCRYPT_RC4_ALGORITHM; } 84 | LPCWSTR GET_BCRYPT_RNG_ALGORITHM() { return BCRYPT_RNG_ALGORITHM; } 85 | LPCWSTR GET_BCRYPT_RNG_DUAL_EC_ALGORITHM() { return BCRYPT_RNG_DUAL_EC_ALGORITHM; } 86 | LPCWSTR GET_BCRYPT_RNG_FIPS186_DSA_ALGORITHM() { return BCRYPT_RNG_FIPS186_DSA_ALGORITHM; } 87 | LPCWSTR GET_BCRYPT_RSA_ALGORITHM() { return BCRYPT_RSA_ALGORITHM; } 88 | LPCWSTR GET_BCRYPT_RSA_SIGN_ALGORITHM() { return BCRYPT_RSA_SIGN_ALGORITHM; } 89 | LPCWSTR GET_BCRYPT_SHA1_ALGORITHM() { return BCRYPT_SHA1_ALGORITHM; } 90 | LPCWSTR GET_BCRYPT_SHA256_ALGORITHM() { return BCRYPT_SHA256_ALGORITHM; } 91 | LPCWSTR GET_BCRYPT_SHA384_ALGORITHM() { return BCRYPT_SHA384_ALGORITHM; } 92 | LPCWSTR GET_BCRYPT_SHA512_ALGORITHM() { return BCRYPT_SHA512_ALGORITHM; } 93 | LPCWSTR GET_BCRYPT_SP800108_CTR_HMAC_ALGORITHM() { return BCRYPT_SP800108_CTR_HMAC_ALGORITHM; } 94 | LPCWSTR GET_BCRYPT_SP80056A_CONCAT_ALGORITHM() { return BCRYPT_SP80056A_CONCAT_ALGORITHM; } 95 | LPCWSTR GET_BCRYPT_PBKDF2_ALGORITHM() { return BCRYPT_PBKDF2_ALGORITHM; } 96 | 97 | // 98 | // These may be missing from bcrypt.h if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) 99 | // Not sure what that really means... 100 | // 101 | 102 | #ifndef BCRYPT_ECDSA_ALGORITHM 103 | #define BCRYPT_ECDSA_ALGORITHM L"ECDSA" 104 | #endif 105 | 106 | #ifndef BCRYPT_ECDH_ALGORITHM 107 | #define BCRYPT_ECDH_ALGORITHM L"ECDH" 108 | #endif 109 | 110 | #ifndef BCRYPT_XTS_AES_ALGORITHM 111 | #define BCRYPT_XTS_AES_ALGORITHM L"XTS-AES" 112 | #endif 113 | 114 | LPCWSTR GET_BCRYPT_ECDSA_ALGORITHM() { return BCRYPT_ECDSA_ALGORITHM; } 115 | LPCWSTR GET_BCRYPT_ECDH_ALGORITHM() { return BCRYPT_ECDH_ALGORITHM; } 116 | LPCWSTR GET_BCRYPT_XTS_AES_ALGORITHM() { return BCRYPT_XTS_AES_ALGORITHM; } 117 | */ 118 | import "C" 119 | 120 | var ( 121 | // Store name 122 | CERT_STORE_PROV_SYSTEM_W = C.GET_CERT_STORE_PROV_SYSTEM_W() 123 | 124 | // NCRYPT Object Property Names 125 | NCRYPT_ALGORITHM_GROUP_PROPERTY = C.GET_NCRYPT_ALGORITHM_GROUP_PROPERTY() 126 | NCRYPT_ALGORITHM_PROPERTY = C.GET_NCRYPT_ALGORITHM_PROPERTY() 127 | NCRYPT_BLOCK_LENGTH_PROPERTY = C.GET_NCRYPT_BLOCK_LENGTH_PROPERTY() 128 | NCRYPT_CERTIFICATE_PROPERTY = C.GET_NCRYPT_CERTIFICATE_PROPERTY() 129 | NCRYPT_DH_PARAMETERS_PROPERTY = C.GET_NCRYPT_DH_PARAMETERS_PROPERTY() 130 | NCRYPT_EXPORT_POLICY_PROPERTY = C.GET_NCRYPT_EXPORT_POLICY_PROPERTY() 131 | NCRYPT_IMPL_TYPE_PROPERTY = C.GET_NCRYPT_IMPL_TYPE_PROPERTY() 132 | NCRYPT_KEY_TYPE_PROPERTY = C.GET_NCRYPT_KEY_TYPE_PROPERTY() 133 | NCRYPT_KEY_USAGE_PROPERTY = C.GET_NCRYPT_KEY_USAGE_PROPERTY() 134 | NCRYPT_LAST_MODIFIED_PROPERTY = C.GET_NCRYPT_LAST_MODIFIED_PROPERTY() 135 | NCRYPT_LENGTH_PROPERTY = C.GET_NCRYPT_LENGTH_PROPERTY() 136 | NCRYPT_LENGTHS_PROPERTY = C.GET_NCRYPT_LENGTHS_PROPERTY() 137 | NCRYPT_MAX_NAME_LENGTH_PROPERTY = C.GET_NCRYPT_MAX_NAME_LENGTH_PROPERTY() 138 | NCRYPT_NAME_PROPERTY = C.GET_NCRYPT_NAME_PROPERTY() 139 | NCRYPT_PIN_PROMPT_PROPERTY = C.GET_NCRYPT_PIN_PROMPT_PROPERTY() 140 | NCRYPT_PIN_PROPERTY = C.GET_NCRYPT_PIN_PROPERTY() 141 | NCRYPT_PROVIDER_HANDLE_PROPERTY = C.GET_NCRYPT_PROVIDER_HANDLE_PROPERTY() 142 | NCRYPT_READER_PROPERTY = C.GET_NCRYPT_READER_PROPERTY() 143 | NCRYPT_ROOT_CERTSTORE_PROPERTY = C.GET_NCRYPT_ROOT_CERTSTORE_PROPERTY() 144 | NCRYPT_SECURE_PIN_PROPERTY = C.GET_NCRYPT_SECURE_PIN_PROPERTY() 145 | NCRYPT_SECURITY_DESCR_PROPERTY = C.GET_NCRYPT_SECURITY_DESCR_PROPERTY() 146 | NCRYPT_SECURITY_DESCR_SUPPORT_PROPERTY = C.GET_NCRYPT_SECURITY_DESCR_SUPPORT_PROPERTY() 147 | NCRYPT_SMARTCARD_GUID_PROPERTY = C.GET_NCRYPT_SMARTCARD_GUID_PROPERTY() 148 | NCRYPT_UI_POLICY_PROPERTY = C.GET_NCRYPT_UI_POLICY_PROPERTY() 149 | NCRYPT_UNIQUE_NAME_PROPERTY = C.GET_NCRYPT_UNIQUE_NAME_PROPERTY() 150 | NCRYPT_USE_CONTEXT_PROPERTY = C.GET_NCRYPT_USE_CONTEXT_PROPERTY() 151 | NCRYPT_USE_COUNT_ENABLED_PROPERTY = C.GET_NCRYPT_USE_COUNT_ENABLED_PROPERTY() 152 | NCRYPT_USE_COUNT_PROPERTY = C.GET_NCRYPT_USE_COUNT_PROPERTY() 153 | NCRYPT_USER_CERTSTORE_PROPERTY = C.GET_NCRYPT_USER_CERTSTORE_PROPERTY() 154 | NCRYPT_VERSION_PROPERTY = C.GET_NCRYPT_VERSION_PROPERTY() 155 | NCRYPT_WINDOW_HANDLE_PROPERTY = C.GET_NCRYPT_WINDOW_HANDLE_PROPERTY() 156 | 157 | // BCRYPT BLOB Types 158 | BCRYPT_DH_PRIVATE_BLOB = C.GET_BCRYPT_DH_PRIVATE_BLOB() 159 | BCRYPT_DH_PUBLIC_BLOB = C.GET_BCRYPT_DH_PUBLIC_BLOB() 160 | BCRYPT_DSA_PRIVATE_BLOB = C.GET_BCRYPT_DSA_PRIVATE_BLOB() 161 | BCRYPT_DSA_PUBLIC_BLOB = C.GET_BCRYPT_DSA_PUBLIC_BLOB() 162 | BCRYPT_ECCPRIVATE_BLOB = C.GET_BCRYPT_ECCPRIVATE_BLOB() 163 | BCRYPT_ECCPUBLIC_BLOB = C.GET_BCRYPT_ECCPUBLIC_BLOB() 164 | BCRYPT_PUBLIC_KEY_BLOB = C.GET_BCRYPT_PUBLIC_KEY_BLOB() 165 | BCRYPT_PRIVATE_KEY_BLOB = C.GET_BCRYPT_PRIVATE_KEY_BLOB() 166 | BCRYPT_RSAFULLPRIVATE_BLOB = C.GET_BCRYPT_RSAFULLPRIVATE_BLOB() 167 | BCRYPT_RSAPRIVATE_BLOB = C.GET_BCRYPT_RSAPRIVATE_BLOB() 168 | BCRYPT_RSAPUBLIC_BLOB = C.GET_BCRYPT_RSAPUBLIC_BLOB() 169 | 170 | // BCRYPT Algorithm Names 171 | BCRYPT_3DES_ALGORITHM = C.GET_BCRYPT_3DES_ALGORITHM() 172 | BCRYPT_3DES_112_ALGORITHM = C.GET_BCRYPT_3DES_112_ALGORITHM() 173 | BCRYPT_AES_ALGORITHM = C.GET_BCRYPT_AES_ALGORITHM() 174 | BCRYPT_AES_CMAC_ALGORITHM = C.GET_BCRYPT_AES_CMAC_ALGORITHM() 175 | BCRYPT_AES_GMAC_ALGORITHM = C.GET_BCRYPT_AES_GMAC_ALGORITHM() 176 | BCRYPT_CAPI_KDF_ALGORITHM = C.GET_BCRYPT_CAPI_KDF_ALGORITHM() 177 | BCRYPT_DES_ALGORITHM = C.GET_BCRYPT_DES_ALGORITHM() 178 | BCRYPT_DESX_ALGORITHM = C.GET_BCRYPT_DESX_ALGORITHM() 179 | BCRYPT_DH_ALGORITHM = C.GET_BCRYPT_DH_ALGORITHM() 180 | BCRYPT_DSA_ALGORITHM = C.GET_BCRYPT_DSA_ALGORITHM() 181 | BCRYPT_ECDH_P256_ALGORITHM = C.GET_BCRYPT_ECDH_P256_ALGORITHM() 182 | BCRYPT_ECDH_P384_ALGORITHM = C.GET_BCRYPT_ECDH_P384_ALGORITHM() 183 | BCRYPT_ECDH_P521_ALGORITHM = C.GET_BCRYPT_ECDH_P521_ALGORITHM() 184 | BCRYPT_ECDSA_P256_ALGORITHM = C.GET_BCRYPT_ECDSA_P256_ALGORITHM() 185 | BCRYPT_ECDSA_P384_ALGORITHM = C.GET_BCRYPT_ECDSA_P384_ALGORITHM() 186 | BCRYPT_ECDSA_P521_ALGORITHM = C.GET_BCRYPT_ECDSA_P521_ALGORITHM() 187 | BCRYPT_MD2_ALGORITHM = C.GET_BCRYPT_MD2_ALGORITHM() 188 | BCRYPT_MD4_ALGORITHM = C.GET_BCRYPT_MD4_ALGORITHM() 189 | BCRYPT_MD5_ALGORITHM = C.GET_BCRYPT_MD5_ALGORITHM() 190 | BCRYPT_RC2_ALGORITHM = C.GET_BCRYPT_RC2_ALGORITHM() 191 | BCRYPT_RC4_ALGORITHM = C.GET_BCRYPT_RC4_ALGORITHM() 192 | BCRYPT_RNG_ALGORITHM = C.GET_BCRYPT_RNG_ALGORITHM() 193 | BCRYPT_RNG_DUAL_EC_ALGORITHM = C.GET_BCRYPT_RNG_DUAL_EC_ALGORITHM() 194 | BCRYPT_RNG_FIPS186_DSA_ALGORITHM = C.GET_BCRYPT_RNG_FIPS186_DSA_ALGORITHM() 195 | BCRYPT_RSA_ALGORITHM = C.GET_BCRYPT_RSA_ALGORITHM() 196 | BCRYPT_RSA_SIGN_ALGORITHM = C.GET_BCRYPT_RSA_SIGN_ALGORITHM() 197 | BCRYPT_SHA1_ALGORITHM = C.GET_BCRYPT_SHA1_ALGORITHM() 198 | BCRYPT_SHA256_ALGORITHM = C.GET_BCRYPT_SHA256_ALGORITHM() 199 | BCRYPT_SHA384_ALGORITHM = C.GET_BCRYPT_SHA384_ALGORITHM() 200 | BCRYPT_SHA512_ALGORITHM = C.GET_BCRYPT_SHA512_ALGORITHM() 201 | BCRYPT_SP800108_CTR_HMAC_ALGORITHM = C.GET_BCRYPT_SP800108_CTR_HMAC_ALGORITHM() 202 | BCRYPT_SP80056A_CONCAT_ALGORITHM = C.GET_BCRYPT_SP80056A_CONCAT_ALGORITHM() 203 | BCRYPT_PBKDF2_ALGORITHM = C.GET_BCRYPT_PBKDF2_ALGORITHM() 204 | BCRYPT_ECDSA_ALGORITHM = C.GET_BCRYPT_ECDSA_ALGORITHM() 205 | BCRYPT_ECDH_ALGORITHM = C.GET_BCRYPT_ECDH_ALGORITHM() 206 | BCRYPT_XTS_AES_ALGORITHM = C.GET_BCRYPT_XTS_AES_ALGORITHM() 207 | ) 208 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // Deprecated: Use the "github.com/github/smimesign/certstore" module instead. 2 | module github.com/github/certstore 3 | 4 | go 1.12 5 | 6 | require ( 7 | github.com/github/fakeca v0.1.0 8 | github.com/pkg/errors v0.8.1 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= 2 | github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= 3 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 4 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "testing" 11 | 12 | "github.com/github/fakeca" 13 | ) 14 | 15 | var ( 16 | root = fakeca.New(fakeca.IsCA, fakeca.Subject(pkix.Name{ 17 | Organization: []string{"certstore"}, 18 | CommonName: "root", 19 | })) 20 | 21 | intermediate = root.Issue(fakeca.IsCA, fakeca.Subject(pkix.Name{ 22 | Organization: []string{"certstore"}, 23 | CommonName: "intermediate", 24 | })) 25 | 26 | leafKeyRSA, _ = rsa.GenerateKey(rand.Reader, 2048) 27 | leafRSA = intermediate.Issue(fakeca.PrivateKey(leafKeyRSA), fakeca.Subject(pkix.Name{ 28 | Organization: []string{"certstore"}, 29 | CommonName: "leaf-rsa", 30 | })) 31 | 32 | leafKeyEC, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 33 | leafEC = intermediate.Issue(fakeca.PrivateKey(leafKeyEC), fakeca.Subject(pkix.Name{ 34 | Organization: []string{"certstore"}, 35 | CommonName: "leaf-ec", 36 | })) 37 | ) 38 | 39 | func init() { 40 | // delete any fixtures from a previous test run. 41 | clearFixtures() 42 | } 43 | 44 | func withStore(t *testing.T, cb func(Store)) { 45 | store, err := Open() 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | defer store.Close() 50 | 51 | cb(store) 52 | } 53 | 54 | func withIdentity(t *testing.T, i *fakeca.Identity, cb func(Identity)) { 55 | withStore(t, func(store Store) { 56 | // Import an identity 57 | if err := store.Import(i.PFX("asdf"), "asdf"); err != nil { 58 | t.Fatal(err) 59 | } 60 | 61 | // Look for our imported identity 62 | idents, err := store.Identities() 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | for _, ident := range idents { 67 | defer ident.Close() 68 | } 69 | 70 | var found Identity 71 | for _, ident := range idents { 72 | crt, err := ident.Certificate() 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | 77 | if i.Certificate.Equal(crt) { 78 | if found != nil { 79 | t.Fatal("duplicate identity imported") 80 | } 81 | found = ident 82 | } 83 | } 84 | if found == nil { 85 | t.Fatal("imported identity not found") 86 | } 87 | 88 | // Clean up after ourselves. 89 | defer func(f Identity) { 90 | if err := f.Delete(); err != nil { 91 | t.Fatal(err) 92 | } 93 | }(found) 94 | 95 | cb(found) 96 | }) 97 | } 98 | 99 | func clearFixtures() { 100 | store, err := Open() 101 | if err != nil { 102 | panic(err) 103 | } 104 | defer store.Close() 105 | 106 | idents, err := store.Identities() 107 | if err != nil { 108 | panic(err) 109 | } 110 | for _, ident := range idents { 111 | defer ident.Close() 112 | } 113 | 114 | for _, ident := range idents { 115 | crt, err := ident.Certificate() 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | if isFixture(crt) { 121 | if err := ident.Delete(); err != nil { 122 | panic(err) 123 | } 124 | } 125 | } 126 | } 127 | 128 | func isFixture(crt *x509.Certificate) bool { 129 | return len(crt.Subject.Organization) == 1 && crt.Subject.Organization[0] == "certstore" 130 | } 131 | -------------------------------------------------------------------------------- /main_windows_test.go: -------------------------------------------------------------------------------- 1 | package certstore 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestMain(m *testing.M) { 10 | // Prefer CryptoAPI 11 | fmt.Println("CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG") 12 | winAPIFlag = 0x00010000 13 | if status := m.Run(); status != 0 { 14 | os.Exit(status) 15 | } 16 | 17 | // Prefer CNG 18 | fmt.Println("CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG") 19 | winAPIFlag = 0x00020000 20 | if status := m.Run(); status != 0 { 21 | os.Exit(status) 22 | } 23 | 24 | os.Exit(0) 25 | } 26 | --------------------------------------------------------------------------------