Remove "docker engine" subcommands

These subcommands were created to allow upgrading a Docker Community
engine to Docker Enterprise, but never really took off.

This patch removes the `docker engine` subcommands, as they added
quite some complexity / additional code.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2019-11-28 16:54:51 +01:00
parent 9ef0c7a9dd
commit 43b2f52d0c
294 changed files with 29 additions and 68620 deletions

View File

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,18 +0,0 @@
# libtrust
Libtrust is library for managing authentication and authorization using public key cryptography.
Authentication is handled using the identity attached to the public key.
Libtrust provides multiple methods to prove possession of the private key associated with an identity.
- TLS x509 certificates
- Signature verification
- Key Challenge
Authorization and access control is managed through a distributed trust graph.
Trust servers are used as the authorities of the trust graph and allow caching portions of the graph for faster access.
## Copyright and license
Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license.
Docs released under Creative commons.

View File

@ -1,175 +0,0 @@
package libtrust
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"time"
)
type certTemplateInfo struct {
commonName string
domains []string
ipAddresses []net.IP
isCA bool
clientAuth bool
serverAuth bool
}
func generateCertTemplate(info *certTemplateInfo) *x509.Certificate {
// Generate a certificate template which is valid from the past week to
// 10 years from now. The usage of the certificate depends on the
// specified fields in the given certTempInfo object.
var (
keyUsage x509.KeyUsage
extKeyUsage []x509.ExtKeyUsage
)
if info.isCA {
keyUsage = x509.KeyUsageCertSign
}
if info.clientAuth {
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageClientAuth)
}
if info.serverAuth {
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth)
}
return &x509.Certificate{
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: info.commonName,
},
NotBefore: time.Now().Add(-time.Hour * 24 * 7),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
DNSNames: info.domains,
IPAddresses: info.ipAddresses,
IsCA: info.isCA,
KeyUsage: keyUsage,
ExtKeyUsage: extKeyUsage,
BasicConstraintsValid: info.isCA,
}
}
func generateCert(pub PublicKey, priv PrivateKey, subInfo, issInfo *certTemplateInfo) (cert *x509.Certificate, err error) {
pubCertTemplate := generateCertTemplate(subInfo)
privCertTemplate := generateCertTemplate(issInfo)
certDER, err := x509.CreateCertificate(
rand.Reader, pubCertTemplate, privCertTemplate,
pub.CryptoPublicKey(), priv.CryptoPrivateKey(),
)
if err != nil {
return nil, fmt.Errorf("failed to create certificate: %s", err)
}
cert, err = x509.ParseCertificate(certDER)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %s", err)
}
return
}
// GenerateSelfSignedServerCert creates a self-signed certificate for the
// given key which is to be used for TLS servers with the given domains and
// IP addresses.
func GenerateSelfSignedServerCert(key PrivateKey, domains []string, ipAddresses []net.IP) (*x509.Certificate, error) {
info := &certTemplateInfo{
commonName: key.KeyID(),
domains: domains,
ipAddresses: ipAddresses,
serverAuth: true,
}
return generateCert(key.PublicKey(), key, info, info)
}
// GenerateSelfSignedClientCert creates a self-signed certificate for the
// given key which is to be used for TLS clients.
func GenerateSelfSignedClientCert(key PrivateKey) (*x509.Certificate, error) {
info := &certTemplateInfo{
commonName: key.KeyID(),
clientAuth: true,
}
return generateCert(key.PublicKey(), key, info, info)
}
// GenerateCACert creates a certificate which can be used as a trusted
// certificate authority.
func GenerateCACert(signer PrivateKey, trustedKey PublicKey) (*x509.Certificate, error) {
subjectInfo := &certTemplateInfo{
commonName: trustedKey.KeyID(),
isCA: true,
}
issuerInfo := &certTemplateInfo{
commonName: signer.KeyID(),
}
return generateCert(trustedKey, signer, subjectInfo, issuerInfo)
}
// GenerateCACertPool creates a certificate authority pool to be used for a
// TLS configuration. Any self-signed certificates issued by the specified
// trusted keys will be verified during a TLS handshake
func GenerateCACertPool(signer PrivateKey, trustedKeys []PublicKey) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
for _, trustedKey := range trustedKeys {
cert, err := GenerateCACert(signer, trustedKey)
if err != nil {
return nil, fmt.Errorf("failed to generate CA certificate: %s", err)
}
certPool.AddCert(cert)
}
return certPool, nil
}
// LoadCertificateBundle loads certificates from the given file. The file should be pem encoded
// containing one or more certificates. The expected pem type is "CERTIFICATE".
func LoadCertificateBundle(filename string) ([]*x509.Certificate, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
certificates := []*x509.Certificate{}
var block *pem.Block
block, b = pem.Decode(b)
for ; block != nil; block, b = pem.Decode(b) {
if block.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, cert)
} else {
return nil, fmt.Errorf("invalid pem block type: %s", block.Type)
}
}
return certificates, nil
}
// LoadCertificatePool loads a CA pool from the given file. The file should be pem encoded
// containing one or more certificates. The expected pem type is "CERTIFICATE".
func LoadCertificatePool(filename string) (*x509.CertPool, error) {
certs, err := LoadCertificateBundle(filename)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
for _, cert := range certs {
pool.AddCert(cert)
}
return pool, nil
}

View File

@ -1,9 +0,0 @@
/*
Package libtrust provides an interface for managing authentication and
authorization using public key cryptography. Authentication is handled
using the identity attached to the public key and verified through TLS
x509 certificates, a key challenge, or signature. Authorization and
access control is managed through a trust graph distributed between
both remote trust servers and locally cached and managed data.
*/
package libtrust

View File

@ -1,428 +0,0 @@
package libtrust
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
)
/*
* EC DSA PUBLIC KEY
*/
// ecPublicKey implements a libtrust.PublicKey using elliptic curve digital
// signature algorithms.
type ecPublicKey struct {
*ecdsa.PublicKey
curveName string
signatureAlgorithm *signatureAlgorithm
extended map[string]interface{}
}
func fromECPublicKey(cryptoPublicKey *ecdsa.PublicKey) (*ecPublicKey, error) {
curve := cryptoPublicKey.Curve
switch {
case curve == elliptic.P256():
return &ecPublicKey{cryptoPublicKey, "P-256", es256, map[string]interface{}{}}, nil
case curve == elliptic.P384():
return &ecPublicKey{cryptoPublicKey, "P-384", es384, map[string]interface{}{}}, nil
case curve == elliptic.P521():
return &ecPublicKey{cryptoPublicKey, "P-521", es512, map[string]interface{}{}}, nil
default:
return nil, errors.New("unsupported elliptic curve")
}
}
// KeyType returns the key type for elliptic curve keys, i.e., "EC".
func (k *ecPublicKey) KeyType() string {
return "EC"
}
// CurveName returns the elliptic curve identifier.
// Possible values are "P-256", "P-384", and "P-521".
func (k *ecPublicKey) CurveName() string {
return k.curveName
}
// KeyID returns a distinct identifier which is unique to this Public Key.
func (k *ecPublicKey) KeyID() string {
return keyIDFromCryptoKey(k)
}
func (k *ecPublicKey) String() string {
return fmt.Sprintf("EC Public Key <%s>", k.KeyID())
}
// Verify verifyies the signature of the data in the io.Reader using this
// PublicKey. The alg parameter should identify the digital signature
// algorithm which was used to produce the signature and should be supported
// by this public key. Returns a nil error if the signature is valid.
func (k *ecPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
// For EC keys there is only one supported signature algorithm depending
// on the curve parameters.
if k.signatureAlgorithm.HeaderParam() != alg {
return fmt.Errorf("unable to verify signature: EC Public Key with curve %q does not support signature algorithm %q", k.curveName, alg)
}
// signature is the concatenation of (r, s), base64Url encoded.
sigLength := len(signature)
expectedOctetLength := 2 * ((k.Params().BitSize + 7) >> 3)
if sigLength != expectedOctetLength {
return fmt.Errorf("signature length is %d octets long, should be %d", sigLength, expectedOctetLength)
}
rBytes, sBytes := signature[:sigLength/2], signature[sigLength/2:]
r := new(big.Int).SetBytes(rBytes)
s := new(big.Int).SetBytes(sBytes)
hasher := k.signatureAlgorithm.HashID().New()
_, err := io.Copy(hasher, data)
if err != nil {
return fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
if !ecdsa.Verify(k.PublicKey, hash, r, s) {
return errors.New("invalid signature")
}
return nil
}
// CryptoPublicKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *ecPublicKey) CryptoPublicKey() crypto.PublicKey {
return k.PublicKey
}
func (k *ecPublicKey) toMap() map[string]interface{} {
jwk := make(map[string]interface{})
for k, v := range k.extended {
jwk[k] = v
}
jwk["kty"] = k.KeyType()
jwk["kid"] = k.KeyID()
jwk["crv"] = k.CurveName()
xBytes := k.X.Bytes()
yBytes := k.Y.Bytes()
octetLength := (k.Params().BitSize + 7) >> 3
// MUST include leading zeros in the output so that x, y are each
// *octetLength* bytes long.
xBuf := make([]byte, octetLength-len(xBytes), octetLength)
yBuf := make([]byte, octetLength-len(yBytes), octetLength)
xBuf = append(xBuf, xBytes...)
yBuf = append(yBuf, yBytes...)
jwk["x"] = joseBase64UrlEncode(xBuf)
jwk["y"] = joseBase64UrlEncode(yBuf)
return jwk
}
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
// elliptic curve keys.
func (k *ecPublicKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
func (k *ecPublicKey) PEMBlock() (*pem.Block, error) {
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
if err != nil {
return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err)
}
k.extended["kid"] = k.KeyID() // For display purposes.
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
}
func (k *ecPublicKey) AddExtendedField(field string, value interface{}) {
k.extended[field] = value
}
func (k *ecPublicKey) GetExtendedField(field string) interface{} {
v, ok := k.extended[field]
if !ok {
return nil
}
return v
}
func ecPublicKeyFromMap(jwk map[string]interface{}) (*ecPublicKey, error) {
// JWK key type (kty) has already been determined to be "EC".
// Need to extract 'crv', 'x', 'y', and 'kid' and check for
// consistency.
// Get the curve identifier value.
crv, err := stringFromMap(jwk, "crv")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key curve identifier: %s", err)
}
var (
curve elliptic.Curve
sigAlg *signatureAlgorithm
)
switch {
case crv == "P-256":
curve = elliptic.P256()
sigAlg = es256
case crv == "P-384":
curve = elliptic.P384()
sigAlg = es384
case crv == "P-521":
curve = elliptic.P521()
sigAlg = es512
default:
return nil, fmt.Errorf("JWK EC Public Key curve identifier not supported: %q\n", crv)
}
// Get the X and Y coordinates for the public key point.
xB64Url, err := stringFromMap(jwk, "x")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
}
x, err := parseECCoordinate(xB64Url, curve)
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
}
yB64Url, err := stringFromMap(jwk, "y")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
}
y, err := parseECCoordinate(yB64Url, curve)
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
}
key := &ecPublicKey{
PublicKey: &ecdsa.PublicKey{Curve: curve, X: x, Y: y},
curveName: crv, signatureAlgorithm: sigAlg,
}
// Key ID is optional too, but if it exists, it should match the key.
_, ok := jwk["kid"]
if ok {
kid, err := stringFromMap(jwk, "kid")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key ID: %s", err)
}
if kid != key.KeyID() {
return nil, fmt.Errorf("JWK EC Public Key ID does not match: %s", kid)
}
}
key.extended = jwk
return key, nil
}
/*
* EC DSA PRIVATE KEY
*/
// ecPrivateKey implements a JWK Private Key using elliptic curve digital signature
// algorithms.
type ecPrivateKey struct {
ecPublicKey
*ecdsa.PrivateKey
}
func fromECPrivateKey(cryptoPrivateKey *ecdsa.PrivateKey) (*ecPrivateKey, error) {
publicKey, err := fromECPublicKey(&cryptoPrivateKey.PublicKey)
if err != nil {
return nil, err
}
return &ecPrivateKey{*publicKey, cryptoPrivateKey}, nil
}
// PublicKey returns the Public Key data associated with this Private Key.
func (k *ecPrivateKey) PublicKey() PublicKey {
return &k.ecPublicKey
}
func (k *ecPrivateKey) String() string {
return fmt.Sprintf("EC Private Key <%s>", k.KeyID())
}
// Sign signs the data read from the io.Reader using a signature algorithm supported
// by the elliptic curve private key. If the specified hashing algorithm is
// supported by this key, that hash function is used to generate the signature
// otherwise the the default hashing algorithm for this key is used. Returns
// the signature and the name of the JWK signature algorithm used, e.g.,
// "ES256", "ES384", "ES512".
func (k *ecPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
// Generate a signature of the data using the internal alg.
// The given hashId is only a suggestion, and since EC keys only support
// on signature/hash algorithm given the curve name, we disregard it for
// the elliptic curve JWK signature implementation.
hasher := k.signatureAlgorithm.HashID().New()
_, err = io.Copy(hasher, data)
if err != nil {
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
r, s, err := ecdsa.Sign(rand.Reader, k.PrivateKey, hash)
if err != nil {
return nil, "", fmt.Errorf("error producing signature: %s", err)
}
rBytes, sBytes := r.Bytes(), s.Bytes()
octetLength := (k.ecPublicKey.Params().BitSize + 7) >> 3
// MUST include leading zeros in the output
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
rBuf = append(rBuf, rBytes...)
sBuf = append(sBuf, sBytes...)
signature = append(rBuf, sBuf...)
alg = k.signatureAlgorithm.HeaderParam()
return
}
// CryptoPrivateKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *ecPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
return k.PrivateKey
}
func (k *ecPrivateKey) toMap() map[string]interface{} {
jwk := k.ecPublicKey.toMap()
dBytes := k.D.Bytes()
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
// octets (where n is the order of the curve). This is because the private
// key d must be in the interval [1, n-1] so the bitlength of d should be
// no larger than the bitlength of n-1. The easiest way to find the octet
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
// bit sequence right by 3, which is essentially dividing by 8 and adding
// 1 if there is any remainder. Thus, the private key value d should be
// output to (bitlength(n-1)+7)>>3 octets.
n := k.ecPublicKey.Params().N
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
// Create a buffer with the necessary zero-padding.
dBuf := make([]byte, octetLength-len(dBytes), octetLength)
dBuf = append(dBuf, dBytes...)
jwk["d"] = joseBase64UrlEncode(dBuf)
return jwk
}
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
// elliptic curve keys.
func (k *ecPrivateKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) {
derBytes, err := x509.MarshalECPrivateKey(k.PrivateKey)
if err != nil {
return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err)
}
k.extended["keyID"] = k.KeyID() // For display purposes.
return createPemBlock("EC PRIVATE KEY", derBytes, k.extended)
}
func ecPrivateKeyFromMap(jwk map[string]interface{}) (*ecPrivateKey, error) {
dB64Url, err := stringFromMap(jwk, "d")
if err != nil {
return nil, fmt.Errorf("JWK EC Private Key: %s", err)
}
// JWK key type (kty) has already been determined to be "EC".
// Need to extract the public key information, then extract the private
// key value 'd'.
publicKey, err := ecPublicKeyFromMap(jwk)
if err != nil {
return nil, err
}
d, err := parseECPrivateParam(dB64Url, publicKey.Curve)
if err != nil {
return nil, fmt.Errorf("JWK EC Private Key d-param: %s", err)
}
key := &ecPrivateKey{
ecPublicKey: *publicKey,
PrivateKey: &ecdsa.PrivateKey{
PublicKey: *publicKey.PublicKey,
D: d,
},
}
return key, nil
}
/*
* Key Generation Functions.
*/
func generateECPrivateKey(curve elliptic.Curve) (k *ecPrivateKey, err error) {
k = new(ecPrivateKey)
k.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, err
}
k.ecPublicKey.PublicKey = &k.PrivateKey.PublicKey
k.extended = make(map[string]interface{})
return
}
// GenerateECP256PrivateKey generates a key pair using elliptic curve P-256.
func GenerateECP256PrivateKey() (PrivateKey, error) {
k, err := generateECPrivateKey(elliptic.P256())
if err != nil {
return nil, fmt.Errorf("error generating EC P-256 key: %s", err)
}
k.curveName = "P-256"
k.signatureAlgorithm = es256
return k, nil
}
// GenerateECP384PrivateKey generates a key pair using elliptic curve P-384.
func GenerateECP384PrivateKey() (PrivateKey, error) {
k, err := generateECPrivateKey(elliptic.P384())
if err != nil {
return nil, fmt.Errorf("error generating EC P-384 key: %s", err)
}
k.curveName = "P-384"
k.signatureAlgorithm = es384
return k, nil
}
// GenerateECP521PrivateKey generates aß key pair using elliptic curve P-521.
func GenerateECP521PrivateKey() (PrivateKey, error) {
k, err := generateECPrivateKey(elliptic.P521())
if err != nil {
return nil, fmt.Errorf("error generating EC P-521 key: %s", err)
}
k.curveName = "P-521"
k.signatureAlgorithm = es512
return k, nil
}

View File

@ -1,50 +0,0 @@
package libtrust
import (
"path/filepath"
)
// FilterByHosts filters the list of PublicKeys to only those which contain a
// 'hosts' pattern which matches the given host. If *includeEmpty* is true,
// then keys which do not specify any hosts are also returned.
func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKey, error) {
filtered := make([]PublicKey, 0, len(keys))
for _, pubKey := range keys {
var hosts []string
switch v := pubKey.GetExtendedField("hosts").(type) {
case []string:
hosts = v
case []interface{}:
for _, value := range v {
h, ok := value.(string)
if !ok {
continue
}
hosts = append(hosts, h)
}
}
if len(hosts) == 0 {
if includeEmpty {
filtered = append(filtered, pubKey)
}
continue
}
// Check if any hosts match pattern
for _, hostPattern := range hosts {
match, err := filepath.Match(hostPattern, host)
if err != nil {
return nil, err
}
if match {
filtered = append(filtered, pubKey)
continue
}
}
}
return filtered, nil
}

View File

@ -1,56 +0,0 @@
package libtrust
import (
"crypto"
_ "crypto/sha256" // Registrer SHA224 and SHA256
_ "crypto/sha512" // Registrer SHA384 and SHA512
"fmt"
)
type signatureAlgorithm struct {
algHeaderParam string
hashID crypto.Hash
}
func (h *signatureAlgorithm) HeaderParam() string {
return h.algHeaderParam
}
func (h *signatureAlgorithm) HashID() crypto.Hash {
return h.hashID
}
var (
rs256 = &signatureAlgorithm{"RS256", crypto.SHA256}
rs384 = &signatureAlgorithm{"RS384", crypto.SHA384}
rs512 = &signatureAlgorithm{"RS512", crypto.SHA512}
es256 = &signatureAlgorithm{"ES256", crypto.SHA256}
es384 = &signatureAlgorithm{"ES384", crypto.SHA384}
es512 = &signatureAlgorithm{"ES512", crypto.SHA512}
)
func rsaSignatureAlgorithmByName(alg string) (*signatureAlgorithm, error) {
switch {
case alg == "RS256":
return rs256, nil
case alg == "RS384":
return rs384, nil
case alg == "RS512":
return rs512, nil
default:
return nil, fmt.Errorf("RSA Digital Signature Algorithm %q not supported", alg)
}
}
func rsaPKCS1v15SignatureAlgorithmForHashID(hashID crypto.Hash) *signatureAlgorithm {
switch {
case hashID == crypto.SHA512:
return rs512
case hashID == crypto.SHA384:
return rs384
case hashID == crypto.SHA256:
fallthrough
default:
return rs256
}
}

View File

@ -1,657 +0,0 @@
package libtrust
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"sort"
"time"
"unicode"
)
var (
// ErrInvalidSignContent is used when the content to be signed is invalid.
ErrInvalidSignContent = errors.New("invalid sign content")
// ErrInvalidJSONContent is used when invalid json is encountered.
ErrInvalidJSONContent = errors.New("invalid json content")
// ErrMissingSignatureKey is used when the specified signature key
// does not exist in the JSON content.
ErrMissingSignatureKey = errors.New("missing signature key")
)
type jsHeader struct {
JWK PublicKey `json:"jwk,omitempty"`
Algorithm string `json:"alg"`
Chain []string `json:"x5c,omitempty"`
}
type jsSignature struct {
Header jsHeader `json:"header"`
Signature string `json:"signature"`
Protected string `json:"protected,omitempty"`
}
type jsSignaturesSorted []jsSignature
func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] }
func (jsbkid jsSignaturesSorted) Len() int { return len(jsbkid) }
func (jsbkid jsSignaturesSorted) Less(i, j int) bool {
ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID()
si, sj := jsbkid[i].Signature, jsbkid[j].Signature
if ki == kj {
return si < sj
}
return ki < kj
}
type signKey struct {
PrivateKey
Chain []*x509.Certificate
}
// JSONSignature represents a signature of a json object.
type JSONSignature struct {
payload string
signatures []jsSignature
indent string
formatLength int
formatTail []byte
}
func newJSONSignature() *JSONSignature {
return &JSONSignature{
signatures: make([]jsSignature, 0, 1),
}
}
// Payload returns the encoded payload of the signature. This
// payload should not be signed directly
func (js *JSONSignature) Payload() ([]byte, error) {
return joseBase64UrlDecode(js.payload)
}
func (js *JSONSignature) protectedHeader() (string, error) {
protected := map[string]interface{}{
"formatLength": js.formatLength,
"formatTail": joseBase64UrlEncode(js.formatTail),
"time": time.Now().UTC().Format(time.RFC3339),
}
protectedBytes, err := json.Marshal(protected)
if err != nil {
return "", err
}
return joseBase64UrlEncode(protectedBytes), nil
}
func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) {
buf := make([]byte, len(js.payload)+len(protectedHeader)+1)
copy(buf, protectedHeader)
buf[len(protectedHeader)] = '.'
copy(buf[len(protectedHeader)+1:], js.payload)
return buf, nil
}
// Sign adds a signature using the given private key.
func (js *JSONSignature) Sign(key PrivateKey) error {
protected, err := js.protectedHeader()
if err != nil {
return err
}
signBytes, err := js.signBytes(protected)
if err != nil {
return err
}
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
if err != nil {
return err
}
js.signatures = append(js.signatures, jsSignature{
Header: jsHeader{
JWK: key.PublicKey(),
Algorithm: algorithm,
},
Signature: joseBase64UrlEncode(sigBytes),
Protected: protected,
})
return nil
}
// SignWithChain adds a signature using the given private key
// and setting the x509 chain. The public key of the first element
// in the chain must be the public key corresponding with the sign key.
func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error {
// Ensure key.Chain[0] is public key for key
//key.Chain.PublicKey
//key.PublicKey().CryptoPublicKey()
// Verify chain
protected, err := js.protectedHeader()
if err != nil {
return err
}
signBytes, err := js.signBytes(protected)
if err != nil {
return err
}
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
if err != nil {
return err
}
header := jsHeader{
Chain: make([]string, len(chain)),
Algorithm: algorithm,
}
for i, cert := range chain {
header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw)
}
js.signatures = append(js.signatures, jsSignature{
Header: header,
Signature: joseBase64UrlEncode(sigBytes),
Protected: protected,
})
return nil
}
// Verify verifies all the signatures and returns the list of
// public keys used to sign. Any x509 chains are not checked.
func (js *JSONSignature) Verify() ([]PublicKey, error) {
keys := make([]PublicKey, len(js.signatures))
for i, signature := range js.signatures {
signBytes, err := js.signBytes(signature.Protected)
if err != nil {
return nil, err
}
var publicKey PublicKey
if len(signature.Header.Chain) > 0 {
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
if err != nil {
return nil, err
}
} else if signature.Header.JWK != nil {
publicKey = signature.Header.JWK
} else {
return nil, errors.New("missing public key")
}
sigBytes, err := joseBase64UrlDecode(signature.Signature)
if err != nil {
return nil, err
}
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
if err != nil {
return nil, err
}
keys[i] = publicKey
}
return keys, nil
}
// VerifyChains verifies all the signatures and the chains associated
// with each signature and returns the list of verified chains.
// Signatures without an x509 chain are not checked.
func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
chains := make([][]*x509.Certificate, 0, len(js.signatures))
for _, signature := range js.signatures {
signBytes, err := js.signBytes(signature.Protected)
if err != nil {
return nil, err
}
var publicKey PublicKey
if len(signature.Header.Chain) > 0 {
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
if err != nil {
return nil, err
}
intermediates := x509.NewCertPool()
if len(signature.Header.Chain) > 1 {
intermediateChain := signature.Header.Chain[1:]
for i := range intermediateChain {
certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i])
if err != nil {
return nil, err
}
intermediate, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
intermediates.AddCert(intermediate)
}
}
verifyOptions := x509.VerifyOptions{
Intermediates: intermediates,
Roots: ca,
}
verifiedChains, err := cert.Verify(verifyOptions)
if err != nil {
return nil, err
}
chains = append(chains, verifiedChains...)
sigBytes, err := joseBase64UrlDecode(signature.Signature)
if err != nil {
return nil, err
}
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
if err != nil {
return nil, err
}
}
}
return chains, nil
}
// JWS returns JSON serialized JWS according to
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2
func (js *JSONSignature) JWS() ([]byte, error) {
if len(js.signatures) == 0 {
return nil, errors.New("missing signature")
}
sort.Sort(jsSignaturesSorted(js.signatures))
jsonMap := map[string]interface{}{
"payload": js.payload,
"signatures": js.signatures,
}
return json.MarshalIndent(jsonMap, "", " ")
}
func notSpace(r rune) bool {
return !unicode.IsSpace(r)
}
func detectJSONIndent(jsonContent []byte) (indent string) {
if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' {
quoteIndex := bytes.IndexRune(jsonContent[1:], '"')
if quoteIndex > 0 {
indent = string(jsonContent[2 : quoteIndex+1])
}
}
return
}
type jsParsedHeader struct {
JWK json.RawMessage `json:"jwk"`
Algorithm string `json:"alg"`
Chain []string `json:"x5c"`
}
type jsParsedSignature struct {
Header jsParsedHeader `json:"header"`
Signature string `json:"signature"`
Protected string `json:"protected"`
}
// ParseJWS parses a JWS serialized JSON object into a Json Signature.
func ParseJWS(content []byte) (*JSONSignature, error) {
type jsParsed struct {
Payload string `json:"payload"`
Signatures []jsParsedSignature `json:"signatures"`
}
parsed := &jsParsed{}
err := json.Unmarshal(content, parsed)
if err != nil {
return nil, err
}
if len(parsed.Signatures) == 0 {
return nil, errors.New("missing signatures")
}
payload, err := joseBase64UrlDecode(parsed.Payload)
if err != nil {
return nil, err
}
js, err := NewJSONSignature(payload)
if err != nil {
return nil, err
}
js.signatures = make([]jsSignature, len(parsed.Signatures))
for i, signature := range parsed.Signatures {
header := jsHeader{
Algorithm: signature.Header.Algorithm,
}
if signature.Header.Chain != nil {
header.Chain = signature.Header.Chain
}
if signature.Header.JWK != nil {
publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK))
if err != nil {
return nil, err
}
header.JWK = publicKey
}
js.signatures[i] = jsSignature{
Header: header,
Signature: signature.Signature,
Protected: signature.Protected,
}
}
return js, nil
}
// NewJSONSignature returns a new unsigned JWS from a json byte array.
// JSONSignature will need to be signed before serializing or storing.
// Optionally, one or more signatures can be provided as byte buffers,
// containing serialized JWS signatures, to assemble a fully signed JWS
// package. It is the callers responsibility to ensure uniqueness of the
// provided signatures.
func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) {
var dataMap map[string]interface{}
err := json.Unmarshal(content, &dataMap)
if err != nil {
return nil, err
}
js := newJSONSignature()
js.indent = detectJSONIndent(content)
js.payload = joseBase64UrlEncode(content)
// Find trailing } and whitespace, put in protected header
closeIndex := bytes.LastIndexFunc(content, notSpace)
if content[closeIndex] != '}' {
return nil, ErrInvalidJSONContent
}
lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace)
if content[lastRuneIndex] == ',' {
return nil, ErrInvalidJSONContent
}
js.formatLength = lastRuneIndex + 1
js.formatTail = content[js.formatLength:]
if len(signatures) > 0 {
for _, signature := range signatures {
var parsedJSig jsParsedSignature
if err := json.Unmarshal(signature, &parsedJSig); err != nil {
return nil, err
}
// TODO(stevvooe): A lot of the code below is repeated in
// ParseJWS. It will require more refactoring to fix that.
jsig := jsSignature{
Header: jsHeader{
Algorithm: parsedJSig.Header.Algorithm,
},
Signature: parsedJSig.Signature,
Protected: parsedJSig.Protected,
}
if parsedJSig.Header.Chain != nil {
jsig.Header.Chain = parsedJSig.Header.Chain
}
if parsedJSig.Header.JWK != nil {
publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK))
if err != nil {
return nil, err
}
jsig.Header.JWK = publicKey
}
js.signatures = append(js.signatures, jsig)
}
}
return js, nil
}
// NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or
// struct. JWS will need to be signed before serializing or storing.
func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) {
switch content.(type) {
case map[string]interface{}:
case struct{}:
default:
return nil, errors.New("invalid data type")
}
js := newJSONSignature()
js.indent = " "
payload, err := json.MarshalIndent(content, "", js.indent)
if err != nil {
return nil, err
}
js.payload = joseBase64UrlEncode(payload)
// Remove '\n}' from formatted section, put in protected header
js.formatLength = len(payload) - 2
js.formatTail = payload[js.formatLength:]
return js, nil
}
func readIntFromMap(key string, m map[string]interface{}) (int, bool) {
value, ok := m[key]
if !ok {
return 0, false
}
switch v := value.(type) {
case int:
return v, true
case float64:
return int(v), true
default:
return 0, false
}
}
func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) {
value, ok := m[key]
if !ok {
return "", false
}
v, ok = value.(string)
return
}
// ParsePrettySignature parses a formatted signature into a
// JSON signature. If the signatures are missing the format information
// an error is thrown. The formatted signature must be created by
// the same method as format signature.
func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) {
var contentMap map[string]json.RawMessage
err := json.Unmarshal(content, &contentMap)
if err != nil {
return nil, fmt.Errorf("error unmarshalling content: %s", err)
}
sigMessage, ok := contentMap[signatureKey]
if !ok {
return nil, ErrMissingSignatureKey
}
var signatureBlocks []jsParsedSignature
err = json.Unmarshal([]byte(sigMessage), &signatureBlocks)
if err != nil {
return nil, fmt.Errorf("error unmarshalling signatures: %s", err)
}
js := newJSONSignature()
js.signatures = make([]jsSignature, len(signatureBlocks))
for i, signatureBlock := range signatureBlocks {
protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected)
if err != nil {
return nil, fmt.Errorf("base64 decode error: %s", err)
}
var protectedHeader map[string]interface{}
err = json.Unmarshal(protectedBytes, &protectedHeader)
if err != nil {
return nil, fmt.Errorf("error unmarshalling protected header: %s", err)
}
formatLength, ok := readIntFromMap("formatLength", protectedHeader)
if !ok {
return nil, errors.New("missing formatted length")
}
encodedTail, ok := readStringFromMap("formatTail", protectedHeader)
if !ok {
return nil, errors.New("missing formatted tail")
}
formatTail, err := joseBase64UrlDecode(encodedTail)
if err != nil {
return nil, fmt.Errorf("base64 decode error on tail: %s", err)
}
if js.formatLength == 0 {
js.formatLength = formatLength
} else if js.formatLength != formatLength {
return nil, errors.New("conflicting format length")
}
if len(js.formatTail) == 0 {
js.formatTail = formatTail
} else if bytes.Compare(js.formatTail, formatTail) != 0 {
return nil, errors.New("conflicting format tail")
}
header := jsHeader{
Algorithm: signatureBlock.Header.Algorithm,
Chain: signatureBlock.Header.Chain,
}
if signatureBlock.Header.JWK != nil {
publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK))
if err != nil {
return nil, fmt.Errorf("error unmarshalling public key: %s", err)
}
header.JWK = publicKey
}
js.signatures[i] = jsSignature{
Header: header,
Signature: signatureBlock.Signature,
Protected: signatureBlock.Protected,
}
}
if js.formatLength > len(content) {
return nil, errors.New("invalid format length")
}
formatted := make([]byte, js.formatLength+len(js.formatTail))
copy(formatted, content[:js.formatLength])
copy(formatted[js.formatLength:], js.formatTail)
js.indent = detectJSONIndent(formatted)
js.payload = joseBase64UrlEncode(formatted)
return js, nil
}
// PrettySignature formats a json signature into an easy to read
// single json serialized object.
func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {
if len(js.signatures) == 0 {
return nil, errors.New("no signatures")
}
payload, err := joseBase64UrlDecode(js.payload)
if err != nil {
return nil, err
}
payload = payload[:js.formatLength]
sort.Sort(jsSignaturesSorted(js.signatures))
var marshalled []byte
var marshallErr error
if js.indent != "" {
marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent)
} else {
marshalled, marshallErr = json.Marshal(js.signatures)
}
if marshallErr != nil {
return nil, marshallErr
}
buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34))
buf.Write(payload)
buf.WriteByte(',')
if js.indent != "" {
buf.WriteByte('\n')
buf.WriteString(js.indent)
buf.WriteByte('"')
buf.WriteString(signatureKey)
buf.WriteString("\": ")
buf.Write(marshalled)
buf.WriteByte('\n')
} else {
buf.WriteByte('"')
buf.WriteString(signatureKey)
buf.WriteString("\":")
buf.Write(marshalled)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// Signatures provides the signatures on this JWS as opaque blobs, sorted by
// keyID. These blobs can be stored and reassembled with payloads. Internally,
// they are simply marshaled json web signatures but implementations should
// not rely on this.
func (js *JSONSignature) Signatures() ([][]byte, error) {
sort.Sort(jsSignaturesSorted(js.signatures))
var sb [][]byte
for _, jsig := range js.signatures {
p, err := json.Marshal(jsig)
if err != nil {
return nil, err
}
sb = append(sb, p)
}
return sb, nil
}
// Merge combines the signatures from one or more other signatures into the
// method receiver. If the payloads differ for any argument, an error will be
// returned and the receiver will not be modified.
func (js *JSONSignature) Merge(others ...*JSONSignature) error {
merged := js.signatures
for _, other := range others {
if js.payload != other.payload {
return fmt.Errorf("payloads differ from merge target")
}
merged = append(merged, other.signatures...)
}
js.signatures = merged
return nil
}

View File

@ -1,253 +0,0 @@
package libtrust
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
)
// PublicKey is a generic interface for a Public Key.
type PublicKey interface {
// KeyType returns the key type for this key. For elliptic curve keys,
// this value should be "EC". For RSA keys, this value should be "RSA".
KeyType() string
// KeyID returns a distinct identifier which is unique to this Public Key.
// The format generated by this library is a base32 encoding of a 240 bit
// hash of the public key data divided into 12 groups like so:
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
KeyID() string
// Verify verifyies the signature of the data in the io.Reader using this
// Public Key. The alg parameter should identify the digital signature
// algorithm which was used to produce the signature and should be
// supported by this public key. Returns a nil error if the signature
// is valid.
Verify(data io.Reader, alg string, signature []byte) error
// CryptoPublicKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
CryptoPublicKey() crypto.PublicKey
// These public keys can be serialized to the standard JSON encoding for
// JSON Web Keys. See section 6 of the IETF draft RFC for JOSE JSON Web
// Algorithms.
MarshalJSON() ([]byte, error)
// These keys can also be serialized to the standard PEM encoding.
PEMBlock() (*pem.Block, error)
// The string representation of a key is its key type and ID.
String() string
AddExtendedField(string, interface{})
GetExtendedField(string) interface{}
}
// PrivateKey is a generic interface for a Private Key.
type PrivateKey interface {
// A PrivateKey contains all fields and methods of a PublicKey of the
// same type. The MarshalJSON method also outputs the private key as a
// JSON Web Key, and the PEMBlock method outputs the private key as a
// PEM block.
PublicKey
// PublicKey returns the PublicKey associated with this PrivateKey.
PublicKey() PublicKey
// Sign signs the data read from the io.Reader using a signature algorithm
// supported by the private key. If the specified hashing algorithm is
// supported by this key, that hash function is used to generate the
// signature otherwise the the default hashing algorithm for this key is
// used. Returns the signature and identifier of the algorithm used.
Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error)
// CryptoPrivateKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The
// type is either *rsa.PublicKey or *ecdsa.PublicKey
CryptoPrivateKey() crypto.PrivateKey
}
// FromCryptoPublicKey returns a libtrust PublicKey representation of the given
// *ecdsa.PublicKey or *rsa.PublicKey. Returns a non-nil error when the given
// key is of an unsupported type.
func FromCryptoPublicKey(cryptoPublicKey crypto.PublicKey) (PublicKey, error) {
switch cryptoPublicKey := cryptoPublicKey.(type) {
case *ecdsa.PublicKey:
return fromECPublicKey(cryptoPublicKey)
case *rsa.PublicKey:
return fromRSAPublicKey(cryptoPublicKey), nil
default:
return nil, fmt.Errorf("public key type %T is not supported", cryptoPublicKey)
}
}
// FromCryptoPrivateKey returns a libtrust PrivateKey representation of the given
// *ecdsa.PrivateKey or *rsa.PrivateKey. Returns a non-nil error when the given
// key is of an unsupported type.
func FromCryptoPrivateKey(cryptoPrivateKey crypto.PrivateKey) (PrivateKey, error) {
switch cryptoPrivateKey := cryptoPrivateKey.(type) {
case *ecdsa.PrivateKey:
return fromECPrivateKey(cryptoPrivateKey)
case *rsa.PrivateKey:
return fromRSAPrivateKey(cryptoPrivateKey), nil
default:
return nil, fmt.Errorf("private key type %T is not supported", cryptoPrivateKey)
}
}
// UnmarshalPublicKeyPEM parses the PEM encoded data and returns a libtrust
// PublicKey or an error if there is a problem with the encoding.
func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, errors.New("unable to find PEM encoded data")
} else if pemBlock.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
}
return pubKeyFromPEMBlock(pemBlock)
}
// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of
// PEM blocks appended one after the other and returns a slice of PublicKey
// objects that it finds.
func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) {
pubKeys := []PublicKey{}
for {
var pemBlock *pem.Block
pemBlock, data = pem.Decode(data)
if pemBlock == nil {
break
} else if pemBlock.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
}
pubKey, err := pubKeyFromPEMBlock(pemBlock)
if err != nil {
return nil, err
}
pubKeys = append(pubKeys, pubKey)
}
return pubKeys, nil
}
// UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust
// PrivateKey or an error if there is a problem with the encoding.
func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, errors.New("unable to find PEM encoded data")
}
var key PrivateKey
switch {
case pemBlock.Type == "RSA PRIVATE KEY":
rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err)
}
key = fromRSAPrivateKey(rsaPrivateKey)
case pemBlock.Type == "EC PRIVATE KEY":
ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err)
}
key, err = fromECPrivateKey(ecPrivateKey)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type)
}
addPEMHeadersToKey(pemBlock, key.PublicKey())
return key, nil
}
// UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic
// Public Key to be used with libtrust.
func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) {
jwk := make(map[string]interface{})
err := json.Unmarshal(data, &jwk)
if err != nil {
return nil, fmt.Errorf(
"decoding JWK Public Key JSON data: %s\n", err,
)
}
// Get the Key Type value.
kty, err := stringFromMap(jwk, "kty")
if err != nil {
return nil, fmt.Errorf("JWK Public Key type: %s", err)
}
switch {
case kty == "EC":
// Call out to unmarshal EC public key.
return ecPublicKeyFromMap(jwk)
case kty == "RSA":
// Call out to unmarshal RSA public key.
return rsaPublicKeyFromMap(jwk)
default:
return nil, fmt.Errorf(
"JWK Public Key type not supported: %q\n", kty,
)
}
}
// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set
// and returns a slice of Public Key objects.
func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) {
rawKeys, err := loadJSONKeySetRaw(data)
if err != nil {
return nil, err
}
pubKeys := make([]PublicKey, 0, len(rawKeys))
for _, rawKey := range rawKeys {
pubKey, err := UnmarshalPublicKeyJWK(rawKey)
if err != nil {
return nil, err
}
pubKeys = append(pubKeys, pubKey)
}
return pubKeys, nil
}
// UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic
// Private Key to be used with libtrust.
func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) {
jwk := make(map[string]interface{})
err := json.Unmarshal(data, &jwk)
if err != nil {
return nil, fmt.Errorf(
"decoding JWK Private Key JSON data: %s\n", err,
)
}
// Get the Key Type value.
kty, err := stringFromMap(jwk, "kty")
if err != nil {
return nil, fmt.Errorf("JWK Private Key type: %s", err)
}
switch {
case kty == "EC":
// Call out to unmarshal EC private key.
return ecPrivateKeyFromMap(jwk)
case kty == "RSA":
// Call out to unmarshal RSA private key.
return rsaPrivateKeyFromMap(jwk)
default:
return nil, fmt.Errorf(
"JWK Private Key type not supported: %q\n", kty,
)
}
}

View File

@ -1,255 +0,0 @@
package libtrust
import (
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
)
var (
// ErrKeyFileDoesNotExist indicates that the private key file does not exist.
ErrKeyFileDoesNotExist = errors.New("key file does not exist")
)
func readKeyFileBytes(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
err = ErrKeyFileDoesNotExist
} else {
err = fmt.Errorf("unable to read key file %s: %s", filename, err)
}
return nil, err
}
return data, nil
}
/*
Loading and Saving of Public and Private Keys in either PEM or JWK format.
*/
// LoadKeyFile opens the given filename and attempts to read a Private Key
// encoded in either PEM or JWK format (if .json or .jwk file extension).
func LoadKeyFile(filename string) (PrivateKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil {
return nil, err
}
var key PrivateKey
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
key, err = UnmarshalPrivateKeyJWK(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode private key JWK: %s", err)
}
} else {
key, err = UnmarshalPrivateKeyPEM(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode private key PEM: %s", err)
}
}
return key, nil
}
// LoadPublicKeyFile opens the given filename and attempts to read a Public Key
// encoded in either PEM or JWK format (if .json or .jwk file extension).
func LoadPublicKeyFile(filename string) (PublicKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil {
return nil, err
}
var key PublicKey
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
key, err = UnmarshalPublicKeyJWK(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode public key JWK: %s", err)
}
} else {
key, err = UnmarshalPublicKeyPEM(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode public key PEM: %s", err)
}
}
return key, nil
}
// SaveKey saves the given key to a file using the provided filename.
// This process will overwrite any existing file at the provided location.
func SaveKey(filename string, key PrivateKey) error {
var encodedKey []byte
var err error
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
// Encode in JSON Web Key format.
encodedKey, err = json.MarshalIndent(key, "", " ")
if err != nil {
return fmt.Errorf("unable to encode private key JWK: %s", err)
}
} else {
// Encode in PEM format.
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encode private key PEM: %s", err)
}
encodedKey = pem.EncodeToMemory(pemBlock)
}
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600))
if err != nil {
return fmt.Errorf("unable to write private key file %s: %s", filename, err)
}
return nil
}
// SavePublicKey saves the given public key to the file.
func SavePublicKey(filename string, key PublicKey) error {
var encodedKey []byte
var err error
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
// Encode in JSON Web Key format.
encodedKey, err = json.MarshalIndent(key, "", " ")
if err != nil {
return fmt.Errorf("unable to encode public key JWK: %s", err)
}
} else {
// Encode in PEM format.
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encode public key PEM: %s", err)
}
encodedKey = pem.EncodeToMemory(pemBlock)
}
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to write public key file %s: %s", filename, err)
}
return nil
}
// Public Key Set files
type jwkSet struct {
Keys []json.RawMessage `json:"keys"`
}
// LoadKeySetFile loads a key set
func LoadKeySetFile(filename string) ([]PublicKey, error) {
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
return loadJSONKeySetFile(filename)
}
// Must be a PEM format file
return loadPEMKeySetFile(filename)
}
func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) {
if len(data) == 0 {
// This is okay, just return an empty slice.
return []json.RawMessage{}, nil
}
keySet := jwkSet{}
err := json.Unmarshal(data, &keySet)
if err != nil {
return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err)
}
return keySet.Keys, nil
}
func loadJSONKeySetFile(filename string) ([]PublicKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return nil, err
}
return UnmarshalPublicKeyJWKSet(contents)
}
func loadPEMKeySetFile(filename string) ([]PublicKey, error) {
data, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return nil, err
}
return UnmarshalPublicKeyPEMBundle(data)
}
// AddKeySetFile adds a key to a key set
func AddKeySetFile(filename string, key PublicKey) error {
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
return addKeySetJSONFile(filename, key)
}
// Must be a PEM format file
return addKeySetPEMFile(filename, key)
}
func addKeySetJSONFile(filename string, key PublicKey) error {
encodedKey, err := json.Marshal(key)
if err != nil {
return fmt.Errorf("unable to encode trusted client key: %s", err)
}
contents, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return err
}
rawEntries, err := loadJSONKeySetRaw(contents)
if err != nil {
return err
}
rawEntries = append(rawEntries, json.RawMessage(encodedKey))
entriesWrapper := jwkSet{Keys: rawEntries}
encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ")
if err != nil {
return fmt.Errorf("unable to encode trusted client keys: %s", err)
}
err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err)
}
return nil
}
func addKeySetPEMFile(filename string, key PublicKey) error {
// Encode to PEM, open file for appending, write PEM.
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err)
}
defer file.Close()
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encoded trusted key: %s", err)
}
_, err = file.Write(pem.EncodeToMemory(pemBlock))
if err != nil {
return fmt.Errorf("unable to write trusted keys file: %s", err)
}
return nil
}

View File

@ -1,175 +0,0 @@
package libtrust
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"os"
"path"
"sync"
)
// ClientKeyManager manages client keys on the filesystem
type ClientKeyManager struct {
key PrivateKey
clientFile string
clientDir string
clientLock sync.RWMutex
clients []PublicKey
configLock sync.Mutex
configs []*tls.Config
}
// NewClientKeyManager loads a new manager from a set of key files
// and managed by the given private key.
func NewClientKeyManager(trustKey PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) {
m := &ClientKeyManager{
key: trustKey,
clientFile: clientFile,
clientDir: clientDir,
}
if err := m.loadKeys(); err != nil {
return nil, err
}
// TODO Start watching file and directory
return m, nil
}
func (c *ClientKeyManager) loadKeys() (err error) {
// Load authorized keys file
var clients []PublicKey
if c.clientFile != "" {
clients, err = LoadKeySetFile(c.clientFile)
if err != nil {
return fmt.Errorf("unable to load authorized keys: %s", err)
}
}
// Add clients from authorized keys directory
files, err := ioutil.ReadDir(c.clientDir)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to open authorized keys directory: %s", err)
}
for _, f := range files {
if !f.IsDir() {
publicKey, err := LoadPublicKeyFile(path.Join(c.clientDir, f.Name()))
if err != nil {
return fmt.Errorf("unable to load authorized key file: %s", err)
}
clients = append(clients, publicKey)
}
}
c.clientLock.Lock()
c.clients = clients
c.clientLock.Unlock()
return nil
}
// RegisterTLSConfig registers a tls configuration to manager
// such that any changes to the keys may be reflected in
// the tls client CA pool
func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error {
c.clientLock.RLock()
certPool, err := GenerateCACertPool(c.key, c.clients)
if err != nil {
return fmt.Errorf("CA pool generation error: %s", err)
}
c.clientLock.RUnlock()
tlsConfig.ClientCAs = certPool
c.configLock.Lock()
c.configs = append(c.configs, tlsConfig)
c.configLock.Unlock()
return nil
}
// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for
// libtrust identity authentication for the domain specified
func NewIdentityAuthTLSConfig(trustKey PrivateKey, clients *ClientKeyManager, addr string, domain string) (*tls.Config, error) {
tlsConfig := newTLSConfig()
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if err := clients.RegisterTLSConfig(tlsConfig); err != nil {
return nil, err
}
// Generate cert
ips, domains, err := parseAddr(addr)
if err != nil {
return nil, err
}
// add domain that it expects clients to use
domains = append(domains, domain)
x509Cert, err := GenerateSelfSignedServerCert(trustKey, domains, ips)
if err != nil {
return nil, fmt.Errorf("certificate generation error: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}}
return tlsConfig, nil
}
// NewCertAuthTLSConfig creates a tls.Config for the server to use for
// certificate authentication
func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
tlsConfig := newTLSConfig()
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
// Verify client certificates against a CA?
if caPath != "" {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = certPool
}
return tlsConfig, nil
}
func newTLSConfig() *tls.Config {
return &tls.Config{
NextProtos: []string{"http/1.1"},
// Avoid fallback on insecure SSL protocols
MinVersion: tls.VersionTLS10,
}
}
// parseAddr parses an address into an array of IPs and domains
func parseAddr(addr string) ([]net.IP, []string, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, nil, err
}
var domains []string
var ips []net.IP
ip := net.ParseIP(host)
if ip != nil {
ips = []net.IP{ip}
} else {
domains = []string{host}
}
return ips, domains, nil
}

View File

@ -1,427 +0,0 @@
package libtrust
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
)
/*
* RSA DSA PUBLIC KEY
*/
// rsaPublicKey implements a JWK Public Key using RSA digital signature algorithms.
type rsaPublicKey struct {
*rsa.PublicKey
extended map[string]interface{}
}
func fromRSAPublicKey(cryptoPublicKey *rsa.PublicKey) *rsaPublicKey {
return &rsaPublicKey{cryptoPublicKey, map[string]interface{}{}}
}
// KeyType returns the JWK key type for RSA keys, i.e., "RSA".
func (k *rsaPublicKey) KeyType() string {
return "RSA"
}
// KeyID returns a distinct identifier which is unique to this Public Key.
func (k *rsaPublicKey) KeyID() string {
return keyIDFromCryptoKey(k)
}
func (k *rsaPublicKey) String() string {
return fmt.Sprintf("RSA Public Key <%s>", k.KeyID())
}
// Verify verifyies the signature of the data in the io.Reader using this Public Key.
// The alg parameter should be the name of the JWA digital signature algorithm
// which was used to produce the signature and should be supported by this
// public key. Returns a nil error if the signature is valid.
func (k *rsaPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
// Verify the signature of the given date, return non-nil error if valid.
sigAlg, err := rsaSignatureAlgorithmByName(alg)
if err != nil {
return fmt.Errorf("unable to verify Signature: %s", err)
}
hasher := sigAlg.HashID().New()
_, err = io.Copy(hasher, data)
if err != nil {
return fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
err = rsa.VerifyPKCS1v15(k.PublicKey, sigAlg.HashID(), hash, signature)
if err != nil {
return fmt.Errorf("invalid %s signature: %s", sigAlg.HeaderParam(), err)
}
return nil
}
// CryptoPublicKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
return k.PublicKey
}
func (k *rsaPublicKey) toMap() map[string]interface{} {
jwk := make(map[string]interface{})
for k, v := range k.extended {
jwk[k] = v
}
jwk["kty"] = k.KeyType()
jwk["kid"] = k.KeyID()
jwk["n"] = joseBase64UrlEncode(k.N.Bytes())
jwk["e"] = joseBase64UrlEncode(serializeRSAPublicExponentParam(k.E))
return jwk
}
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
// RSA keys.
func (k *rsaPublicKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) {
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
if err != nil {
return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err)
}
k.extended["kid"] = k.KeyID() // For display purposes.
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
}
func (k *rsaPublicKey) AddExtendedField(field string, value interface{}) {
k.extended[field] = value
}
func (k *rsaPublicKey) GetExtendedField(field string) interface{} {
v, ok := k.extended[field]
if !ok {
return nil
}
return v
}
func rsaPublicKeyFromMap(jwk map[string]interface{}) (*rsaPublicKey, error) {
// JWK key type (kty) has already been determined to be "RSA".
// Need to extract 'n', 'e', and 'kid' and check for
// consistency.
// Get the modulus parameter N.
nB64Url, err := stringFromMap(jwk, "n")
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
}
n, err := parseRSAModulusParam(nB64Url)
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
}
// Get the public exponent E.
eB64Url, err := stringFromMap(jwk, "e")
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
}
e, err := parseRSAPublicExponentParam(eB64Url)
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
}
key := &rsaPublicKey{
PublicKey: &rsa.PublicKey{N: n, E: e},
}
// Key ID is optional, but if it exists, it should match the key.
_, ok := jwk["kid"]
if ok {
kid, err := stringFromMap(jwk, "kid")
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key ID: %s", err)
}
if kid != key.KeyID() {
return nil, fmt.Errorf("JWK RSA Public Key ID does not match: %s", kid)
}
}
if _, ok := jwk["d"]; ok {
return nil, fmt.Errorf("JWK RSA Public Key cannot contain private exponent")
}
key.extended = jwk
return key, nil
}
/*
* RSA DSA PRIVATE KEY
*/
// rsaPrivateKey implements a JWK Private Key using RSA digital signature algorithms.
type rsaPrivateKey struct {
rsaPublicKey
*rsa.PrivateKey
}
func fromRSAPrivateKey(cryptoPrivateKey *rsa.PrivateKey) *rsaPrivateKey {
return &rsaPrivateKey{
*fromRSAPublicKey(&cryptoPrivateKey.PublicKey),
cryptoPrivateKey,
}
}
// PublicKey returns the Public Key data associated with this Private Key.
func (k *rsaPrivateKey) PublicKey() PublicKey {
return &k.rsaPublicKey
}
func (k *rsaPrivateKey) String() string {
return fmt.Sprintf("RSA Private Key <%s>", k.KeyID())
}
// Sign signs the data read from the io.Reader using a signature algorithm supported
// by the RSA private key. If the specified hashing algorithm is supported by
// this key, that hash function is used to generate the signature otherwise the
// the default hashing algorithm for this key is used. Returns the signature
// and the name of the JWK signature algorithm used, e.g., "RS256", "RS384",
// "RS512".
func (k *rsaPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
// Generate a signature of the data using the internal alg.
sigAlg := rsaPKCS1v15SignatureAlgorithmForHashID(hashID)
hasher := sigAlg.HashID().New()
_, err = io.Copy(hasher, data)
if err != nil {
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
signature, err = rsa.SignPKCS1v15(rand.Reader, k.PrivateKey, sigAlg.HashID(), hash)
if err != nil {
return nil, "", fmt.Errorf("error producing signature: %s", err)
}
alg = sigAlg.HeaderParam()
return
}
// CryptoPrivateKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *rsaPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
return k.PrivateKey
}
func (k *rsaPrivateKey) toMap() map[string]interface{} {
k.Precompute() // Make sure the precomputed values are stored.
jwk := k.rsaPublicKey.toMap()
jwk["d"] = joseBase64UrlEncode(k.D.Bytes())
jwk["p"] = joseBase64UrlEncode(k.Primes[0].Bytes())
jwk["q"] = joseBase64UrlEncode(k.Primes[1].Bytes())
jwk["dp"] = joseBase64UrlEncode(k.Precomputed.Dp.Bytes())
jwk["dq"] = joseBase64UrlEncode(k.Precomputed.Dq.Bytes())
jwk["qi"] = joseBase64UrlEncode(k.Precomputed.Qinv.Bytes())
otherPrimes := k.Primes[2:]
if len(otherPrimes) > 0 {
otherPrimesInfo := make([]interface{}, len(otherPrimes))
for i, r := range otherPrimes {
otherPrimeInfo := make(map[string]string, 3)
otherPrimeInfo["r"] = joseBase64UrlEncode(r.Bytes())
crtVal := k.Precomputed.CRTValues[i]
otherPrimeInfo["d"] = joseBase64UrlEncode(crtVal.Exp.Bytes())
otherPrimeInfo["t"] = joseBase64UrlEncode(crtVal.Coeff.Bytes())
otherPrimesInfo[i] = otherPrimeInfo
}
jwk["oth"] = otherPrimesInfo
}
return jwk
}
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
// RSA keys.
func (k *rsaPrivateKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
func (k *rsaPrivateKey) PEMBlock() (*pem.Block, error) {
derBytes := x509.MarshalPKCS1PrivateKey(k.PrivateKey)
k.extended["keyID"] = k.KeyID() // For display purposes.
return createPemBlock("RSA PRIVATE KEY", derBytes, k.extended)
}
func rsaPrivateKeyFromMap(jwk map[string]interface{}) (*rsaPrivateKey, error) {
// The JWA spec for RSA Private Keys (draft rfc section 5.3.2) states that
// only the private key exponent 'd' is REQUIRED, the others are just for
// signature/decryption optimizations and SHOULD be included when the JWK
// is produced. We MAY choose to accept a JWK which only includes 'd', but
// we're going to go ahead and not choose to accept it without the extra
// fields. Only the 'oth' field will be optional (for multi-prime keys).
privateExponent, err := parseRSAPrivateKeyParamFromMap(jwk, "d")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key exponent: %s", err)
}
firstPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "p")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
}
secondPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "q")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
}
firstFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dp")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
}
secondFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dq")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
}
crtCoeff, err := parseRSAPrivateKeyParamFromMap(jwk, "qi")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
}
var oth interface{}
if _, ok := jwk["oth"]; ok {
oth = jwk["oth"]
delete(jwk, "oth")
}
// JWK key type (kty) has already been determined to be "RSA".
// Need to extract the public key information, then extract the private
// key values.
publicKey, err := rsaPublicKeyFromMap(jwk)
if err != nil {
return nil, err
}
privateKey := &rsa.PrivateKey{
PublicKey: *publicKey.PublicKey,
D: privateExponent,
Primes: []*big.Int{firstPrimeFactor, secondPrimeFactor},
Precomputed: rsa.PrecomputedValues{
Dp: firstFactorCRT,
Dq: secondFactorCRT,
Qinv: crtCoeff,
},
}
if oth != nil {
// Should be an array of more JSON objects.
otherPrimesInfo, ok := oth.([]interface{})
if !ok {
return nil, errors.New("JWK RSA Private Key: Invalid other primes info: must be an array")
}
numOtherPrimeFactors := len(otherPrimesInfo)
if numOtherPrimeFactors == 0 {
return nil, errors.New("JWK RSA Privake Key: Invalid other primes info: must be absent or non-empty")
}
otherPrimeFactors := make([]*big.Int, numOtherPrimeFactors)
productOfPrimes := new(big.Int).Mul(firstPrimeFactor, secondPrimeFactor)
crtValues := make([]rsa.CRTValue, numOtherPrimeFactors)
for i, val := range otherPrimesInfo {
otherPrimeinfo, ok := val.(map[string]interface{})
if !ok {
return nil, errors.New("JWK RSA Private Key: Invalid other prime info: must be a JSON object")
}
otherPrimeFactor, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "r")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
}
otherFactorCRT, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "d")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
}
otherCrtCoeff, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "t")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
}
crtValue := crtValues[i]
crtValue.Exp = otherFactorCRT
crtValue.Coeff = otherCrtCoeff
crtValue.R = productOfPrimes
otherPrimeFactors[i] = otherPrimeFactor
productOfPrimes = new(big.Int).Mul(productOfPrimes, otherPrimeFactor)
}
privateKey.Primes = append(privateKey.Primes, otherPrimeFactors...)
privateKey.Precomputed.CRTValues = crtValues
}
key := &rsaPrivateKey{
rsaPublicKey: *publicKey,
PrivateKey: privateKey,
}
return key, nil
}
/*
* Key Generation Functions.
*/
func generateRSAPrivateKey(bits int) (k *rsaPrivateKey, err error) {
k = new(rsaPrivateKey)
k.PrivateKey, err = rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
k.rsaPublicKey.PublicKey = &k.PrivateKey.PublicKey
k.extended = make(map[string]interface{})
return
}
// GenerateRSA2048PrivateKey generates a key pair using 2048-bit RSA.
func GenerateRSA2048PrivateKey() (PrivateKey, error) {
k, err := generateRSAPrivateKey(2048)
if err != nil {
return nil, fmt.Errorf("error generating RSA 2048-bit key: %s", err)
}
return k, nil
}
// GenerateRSA3072PrivateKey generates a key pair using 3072-bit RSA.
func GenerateRSA3072PrivateKey() (PrivateKey, error) {
k, err := generateRSAPrivateKey(3072)
if err != nil {
return nil, fmt.Errorf("error generating RSA 3072-bit key: %s", err)
}
return k, nil
}
// GenerateRSA4096PrivateKey generates a key pair using 4096-bit RSA.
func GenerateRSA4096PrivateKey() (PrivateKey, error) {
k, err := generateRSAPrivateKey(4096)
if err != nil {
return nil, fmt.Errorf("error generating RSA 4096-bit key: %s", err)
}
return k, nil
}

View File

@ -1,363 +0,0 @@
package libtrust
import (
"bytes"
"crypto"
"crypto/elliptic"
"crypto/tls"
"crypto/x509"
"encoding/base32"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net/url"
"os"
"path/filepath"
"strings"
"time"
)
// LoadOrCreateTrustKey will load a PrivateKey from the specified path
func LoadOrCreateTrustKey(trustKeyPath string) (PrivateKey, error) {
if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil {
return nil, err
}
trustKey, err := LoadKeyFile(trustKeyPath)
if err == ErrKeyFileDoesNotExist {
trustKey, err = GenerateECP256PrivateKey()
if err != nil {
return nil, fmt.Errorf("error generating key: %s", err)
}
if err := SaveKey(trustKeyPath, trustKey); err != nil {
return nil, fmt.Errorf("error saving key file: %s", err)
}
dir, file := filepath.Split(trustKeyPath)
if err := SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil {
return nil, fmt.Errorf("error saving public key file: %s", err)
}
} else if err != nil {
return nil, fmt.Errorf("error loading key file: %s", err)
}
return trustKey, nil
}
// NewIdentityAuthTLSClientConfig returns a tls.Config configured to use identity
// based authentication from the specified dockerUrl, the rootConfigPath and
// the server name to which it is connecting.
// If trustUnknownHosts is true it will automatically add the host to the
// known-hosts.json in rootConfigPath.
func NewIdentityAuthTLSClientConfig(dockerUrl string, trustUnknownHosts bool, rootConfigPath string, serverName string) (*tls.Config, error) {
tlsConfig := newTLSConfig()
trustKeyPath := filepath.Join(rootConfigPath, "key.json")
knownHostsPath := filepath.Join(rootConfigPath, "known-hosts.json")
u, err := url.Parse(dockerUrl)
if err != nil {
return nil, fmt.Errorf("unable to parse machine url")
}
if u.Scheme == "unix" {
return nil, nil
}
addr := u.Host
proto := "tcp"
trustKey, err := LoadOrCreateTrustKey(trustKeyPath)
if err != nil {
return nil, fmt.Errorf("unable to load trust key: %s", err)
}
knownHosts, err := LoadKeySetFile(knownHostsPath)
if err != nil {
return nil, fmt.Errorf("could not load trusted hosts file: %s", err)
}
allowedHosts, err := FilterByHosts(knownHosts, addr, false)
if err != nil {
return nil, fmt.Errorf("error filtering hosts: %s", err)
}
certPool, err := GenerateCACertPool(trustKey, allowedHosts)
if err != nil {
return nil, fmt.Errorf("Could not create CA pool: %s", err)
}
tlsConfig.ServerName = serverName
tlsConfig.RootCAs = certPool
x509Cert, err := GenerateSelfSignedClientCert(trustKey)
if err != nil {
return nil, fmt.Errorf("certificate generation error: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}}
tlsConfig.InsecureSkipVerify = true
testConn, err := tls.Dial(proto, addr, tlsConfig)
if err != nil {
return nil, fmt.Errorf("tls Handshake error: %s", err)
}
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
DNSName: tlsConfig.ServerName,
Intermediates: x509.NewCertPool(),
}
certs := testConn.ConnectionState().PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err != nil {
if _, ok := err.(x509.UnknownAuthorityError); ok {
if trustUnknownHosts {
pubKey, err := FromCryptoPublicKey(certs[0].PublicKey)
if err != nil {
return nil, fmt.Errorf("error extracting public key from cert: %s", err)
}
pubKey.AddExtendedField("hosts", []string{addr})
if err := AddKeySetFile(knownHostsPath, pubKey); err != nil {
return nil, fmt.Errorf("error adding machine to known hosts: %s", err)
}
} else {
return nil, fmt.Errorf("unable to connect. unknown host: %s", addr)
}
}
}
testConn.Close()
tlsConfig.InsecureSkipVerify = false
return tlsConfig, nil
}
// joseBase64UrlEncode encodes the given data using the standard base64 url
// encoding format but with all trailing '=' characters ommitted in accordance
// with the jose specification.
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
func joseBase64UrlEncode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// joseBase64UrlDecode decodes the given string using the standard base64 url
// decoder but first adds the appropriate number of trailing '=' characters in
// accordance with the jose specification.
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
func joseBase64UrlDecode(s string) ([]byte, error) {
s = strings.Replace(s, "\n", "", -1)
s = strings.Replace(s, " ", "", -1)
switch len(s) % 4 {
case 0:
case 2:
s += "=="
case 3:
s += "="
default:
return nil, errors.New("illegal base64url string")
}
return base64.URLEncoding.DecodeString(s)
}
func keyIDEncode(b []byte) string {
s := strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=")
var buf bytes.Buffer
var i int
for i = 0; i < len(s)/4-1; i++ {
start := i * 4
end := start + 4
buf.WriteString(s[start:end] + ":")
}
buf.WriteString(s[i*4:])
return buf.String()
}
func keyIDFromCryptoKey(pubKey PublicKey) string {
// Generate and return a 'libtrust' fingerprint of the public key.
// For an RSA key this should be:
// SHA256(DER encoded ASN1)
// Then truncated to 240 bits and encoded into 12 base32 groups like so:
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey())
if err != nil {
return ""
}
hasher := crypto.SHA256.New()
hasher.Write(derBytes)
return keyIDEncode(hasher.Sum(nil)[:30])
}
func stringFromMap(m map[string]interface{}, key string) (string, error) {
val, ok := m[key]
if !ok {
return "", fmt.Errorf("%q value not specified", key)
}
str, ok := val.(string)
if !ok {
return "", fmt.Errorf("%q value must be a string", key)
}
delete(m, key)
return str, nil
}
func parseECCoordinate(cB64Url string, curve elliptic.Curve) (*big.Int, error) {
curveByteLen := (curve.Params().BitSize + 7) >> 3
cBytes, err := joseBase64UrlDecode(cB64Url)
if err != nil {
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
cByteLength := len(cBytes)
if cByteLength != curveByteLen {
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", cByteLength, curveByteLen)
}
return new(big.Int).SetBytes(cBytes), nil
}
func parseECPrivateParam(dB64Url string, curve elliptic.Curve) (*big.Int, error) {
dBytes, err := joseBase64UrlDecode(dB64Url)
if err != nil {
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
// octets (where n is the order of the curve). This is because the private
// key d must be in the interval [1, n-1] so the bitlength of d should be
// no larger than the bitlength of n-1. The easiest way to find the octet
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
// bit sequence right by 3, which is essentially dividing by 8 and adding
// 1 if there is any remainder. Thus, the private key value d should be
// output to (bitlength(n-1)+7)>>3 octets.
n := curve.Params().N
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
dByteLength := len(dBytes)
if dByteLength != octetLength {
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", dByteLength, octetLength)
}
return new(big.Int).SetBytes(dBytes), nil
}
func parseRSAModulusParam(nB64Url string) (*big.Int, error) {
nBytes, err := joseBase64UrlDecode(nB64Url)
if err != nil {
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
return new(big.Int).SetBytes(nBytes), nil
}
func serializeRSAPublicExponentParam(e int) []byte {
// We MUST use the minimum number of octets to represent E.
// E is supposed to be 65537 for performance and security reasons
// and is what golang's rsa package generates, but it might be
// different if imported from some other generator.
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(e))
var i int
for i = 0; i < 8; i++ {
if buf[i] != 0 {
break
}
}
return buf[i:]
}
func parseRSAPublicExponentParam(eB64Url string) (int, error) {
eBytes, err := joseBase64UrlDecode(eB64Url)
if err != nil {
return 0, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
// Only the minimum number of bytes were used to represent E, but
// binary.BigEndian.Uint32 expects at least 4 bytes, so we need
// to add zero padding if necassary.
byteLen := len(eBytes)
buf := make([]byte, 4-byteLen, 4)
eBytes = append(buf, eBytes...)
return int(binary.BigEndian.Uint32(eBytes)), nil
}
func parseRSAPrivateKeyParamFromMap(m map[string]interface{}, key string) (*big.Int, error) {
b64Url, err := stringFromMap(m, key)
if err != nil {
return nil, err
}
paramBytes, err := joseBase64UrlDecode(b64Url)
if err != nil {
return nil, fmt.Errorf("invaled base64 URL encoding: %s", err)
}
return new(big.Int).SetBytes(paramBytes), nil
}
func createPemBlock(name string, derBytes []byte, headers map[string]interface{}) (*pem.Block, error) {
pemBlock := &pem.Block{Type: name, Bytes: derBytes, Headers: map[string]string{}}
for k, v := range headers {
switch val := v.(type) {
case string:
pemBlock.Headers[k] = val
case []string:
if k == "hosts" {
pemBlock.Headers[k] = strings.Join(val, ",")
} else {
// Return error, non-encodable type
}
default:
// Return error, non-encodable type
}
}
return pemBlock, nil
}
func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) {
cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err)
}
pubKey, err := FromCryptoPublicKey(cryptoPublicKey)
if err != nil {
return nil, err
}
addPEMHeadersToKey(pemBlock, pubKey)
return pubKey, nil
}
func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) {
for key, value := range pemBlock.Headers {
var safeVal interface{}
if key == "hosts" {
safeVal = strings.Split(value, ",")
} else {
safeVal = value
}
pubKey.AddExtendedField(key, safeVal)
}
}

View File

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2013-2017 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,90 +0,0 @@
docker/licensing
=========
## Overview
*licensing* is a library for interacting with Docker issued product licenses. It facilitates user's authentication to the [Docker Hub](https://hub.docker.com), provides a mechanism for retrieving a user's existing docker-issued subscriptions/licenses, detects and verifies locally stored licenses, and can be used to provision trial licenses for [Docker Enterprise Edition](https://www.docker.com/enterprise-edition).
License
=========
docker/licensing is licensed under the Apache License, Version 2.0. See
[LICENSE](https://github.com/docker/licensing/blob/master/LICENSE) for the full
license text.
Usage
========
```go
package main
import (
"context"
"fmt"
"net/url"
"github.com/docker/licensing"
"github.com/docker/licensing/model"
)
const (
hubURL = "https://hub.docker.com"
pubKey = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0Ka2lkOiBKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVAoKTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUF5ZEl5K2xVN283UGNlWSs0K3MrQwpRNU9FZ0N5RjhDeEljUUlXdUs4NHBJaVpjaVk2NzMweUNZbndMU0tUbHcrVTZVQy9RUmVXUmlvTU5ORTVEczVUCllFWGJHRzZvbG0ycWRXYkJ3Y0NnKzJVVUgvT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTEKSXYrbXE3bHA1blEvUnhyVDBLWlJBUVRZTEVNRWZHd20zaE1PL2dlTFBTK2hnS1B0SUhsa2c2L1djb3hUR29LUAo3OWQvd2FIWXhHTmw3V2hTbmVpQlN4YnBiUUFLazIxbGc3OThYYjd2WnlFQVRETXJSUjlNZUU2QWRqNUhKcFkzCkNveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTyt3YTJLTjhVd2dJSW0waTVJMXVXOUdrcTQKempCeTV6aGdxdVVYYkc5YldQQU9ZcnE1UWE4MUR4R2NCbEp5SFlBcCtERFBFOVRHZzR6WW1YakpueFpxSEVkdQpHcWRldlo4WE1JMHVrZmtHSUkxNHdVT2lNSUlJclhsRWNCZi80Nkk4Z1FXRHp4eWNaZS9KR1grTEF1YXlYcnlyClVGZWhWTlVkWlVsOXdYTmFKQitrYUNxejVRd2FSOTNzR3crUVNmdEQwTnZMZTdDeU9IK0U2dmc2U3QvTmVUdmcKdjhZbmhDaVhJbFo4SE9mSXdOZTd0RUYvVWN6NU9iUHlrbTN0eWxyTlVqdDBWeUFtdHRhY1ZJMmlHaWhjVVBybQprNGxWSVo3VkQvTFNXK2k3eW9TdXJ0cHNQWGNlMnBLRElvMzBsSkdoTy8zS1VtbDJTVVpDcXpKMXlFbUtweXNICjVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo="
username = "docker username"
password = "your password"
appFeature = "jump"
)
func panicOnErr(err error) {
if err != nil {
panic(err)
}
}
func main() {
hubURI, err := url.Parse(hubURL)
panicOnErr(err)
// setup client
c, err := licensing.New(&licensing.Config{
BaseURI: *hubURI,
HTTPClient: nil,
PublicKeys: []string{pubKey},
})
panicOnErr(err)
// grab token
ctx := context.Background()
token, err := c.LoginViaAuth(ctx, username, password)
panicOnErr(err)
// fetch dockerID, if not already known
id, err := c.GetHubUserByName(ctx, username)
panicOnErr(err)
subs, err := c.ListSubscriptions(ctx, token, id.ID)
panicOnErr(err)
// find first available subscription with given feature
var featuredSub *model.Subscription
for _, sub := range subs {
_, ok := sub.GetFeatureValue(appFeature)
if ok {
featuredSub = sub
break
}
}
if featuredSub == nil {
fmt.Println("account has no subscriptions with the desired feature entitlements")
return
}
// download license file for this subscription
subLic, err := c.DownloadLicenseFromHub(ctx, token, featuredSub.ID)
panicOnErr(err)
// verify license is issued by corresponding keypair and is not expired
licFile, err := c.VerifyLicense(ctx, *subLic)
panicOnErr(err)
fmt.Println("license summary: ", c.SummarizeLicense(licFile))
}
```

View File

@ -1,276 +0,0 @@
package licensing
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
"strings"
"github.com/docker/libtrust"
"github.com/docker/licensing/lib/errors"
"github.com/docker/licensing/lib/go-auth/jwt"
"github.com/docker/licensing/lib/go-clientlib"
"github.com/docker/licensing/model"
)
const (
trialProductID = "docker-ee-trial"
trialRatePlanID = "free-trial"
)
// Client represents the licensing package interface, including methods for authentication and interaction with Docker
// licensing, accounts, and billing services
type Client interface {
LoginViaAuth(ctx context.Context, username, password string) (authToken string, err error)
GetHubUserOrgs(ctx context.Context, authToken string) (orgs []model.Org, err error)
GetHubUserByName(ctx context.Context, username string) (user *model.User, err error)
VerifyLicense(ctx context.Context, license model.IssuedLicense) (res *model.CheckResponse, err error)
GenerateNewTrialSubscription(ctx context.Context, authToken, dockerID string) (subscriptionID string, err error)
ListSubscriptions(ctx context.Context, authToken, dockerID string) (response []*model.Subscription, err error)
ListSubscriptionsDetails(ctx context.Context, authToken, dockerID string) (response []*model.SubscriptionDetail, err error)
DownloadLicenseFromHub(ctx context.Context, authToken, subscriptionID string) (license *model.IssuedLicense, err error)
ParseLicense(license []byte) (parsedLicense *model.IssuedLicense, err error)
StoreLicense(ctx context.Context, dclnt WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error
LoadLocalLicense(ctx context.Context, dclnt WrappedDockerClient) (*model.Subscription, error)
SummarizeLicense(res *model.CheckResponse) *model.Subscription
}
func (c *client) LoginViaAuth(ctx context.Context, username, password string) (string, error) {
creds, err := c.login(ctx, username, password)
if err != nil {
return "", errors.Wrap(err, errors.Fields{
"username": username,
})
}
return creds.Token, nil
}
func (c *client) GetHubUserOrgs(ctx context.Context, authToken string) ([]model.Org, error) {
ctx = jwt.NewContext(ctx, authToken)
orgs, err := c.getUserOrgs(ctx, model.PaginationParams{})
if err != nil {
return nil, errors.WithMessage(err, "Failed to get orgs for user")
}
return orgs, nil
}
func (c *client) GetHubUserByName(ctx context.Context, username string) (*model.User, error) {
user, err := c.getUserByName(ctx, username)
if err != nil {
return nil, errors.Wrap(err, errors.Fields{
"username": username,
})
}
return user, nil
}
func (c *client) VerifyLicense(ctx context.Context, license model.IssuedLicense) (*model.CheckResponse, error) {
res, err := c.check(ctx, license)
if err != nil {
return nil, errors.WithMessage(err, "Failed to verify license")
}
return res, nil
}
func (c *client) GenerateNewTrialSubscription(ctx context.Context, authToken, dockerID string) (string, error) {
ctx = jwt.NewContext(ctx, authToken)
sub, err := c.createSubscription(ctx, &model.SubscriptionCreationRequest{
Name: "Docker Enterprise Free Trial",
DockerID: dockerID,
ProductID: trialProductID,
ProductRatePlan: trialRatePlanID,
Eusa: &model.EusaState{
Accepted: true,
},
})
if err != nil {
return "", errors.Wrap(err, errors.Fields{
"docker_id": dockerID,
})
}
return sub.ID, nil
}
// ListSubscriptions returns basic descriptions of all subscriptions to docker enterprise products for the given dockerID
func (c *client) ListSubscriptions(ctx context.Context, authToken, dockerID string) ([]*model.Subscription, error) {
ctx = jwt.NewContext(ctx, authToken)
subs, err := c.listSubscriptions(ctx, map[string]string{"docker_id": dockerID})
if err != nil {
return nil, errors.Wrap(err, errors.Fields{
"docker_id": dockerID,
})
}
// filter out non docker licenses
dockerSubs := []*model.Subscription{}
for _, sub := range subs {
if !strings.HasPrefix(sub.ProductID, "docker") {
continue
}
dockerSubs = append(dockerSubs, sub)
}
return dockerSubs, nil
}
// ListDetailedSubscriptions returns detailed subscriptions to docker enterprise products for the given dockerID
func (c *client) ListSubscriptionsDetails(ctx context.Context, authToken, dockerID string) ([]*model.SubscriptionDetail, error) {
ctx = jwt.NewContext(ctx, authToken)
subs, err := c.listSubscriptionsDetails(ctx, map[string]string{"docker_id": dockerID})
if err != nil {
return nil, errors.Wrap(err, errors.Fields{
"docker_id": dockerID,
})
}
// filter out non docker licenses
dockerSubs := []*model.SubscriptionDetail{}
for _, sub := range subs {
if !strings.HasPrefix(sub.ProductID, "docker") {
continue
}
dockerSubs = append(dockerSubs, sub)
}
return dockerSubs, nil
}
func (c *client) DownloadLicenseFromHub(ctx context.Context, authToken, subscriptionID string) (*model.IssuedLicense, error) {
ctx = jwt.NewContext(ctx, authToken)
license, err := c.getLicenseFile(ctx, subscriptionID)
if err != nil {
return nil, errors.Wrap(err, errors.Fields{
"subscriptionID": subscriptionID,
})
}
return license, nil
}
func (c *client) ParseLicense(license []byte) (*model.IssuedLicense, error) {
parsedLicense := &model.IssuedLicense{}
// The file may contain a leading BOM, which will choke the
// json deserializer.
license = bytes.Trim(license, "\xef\xbb\xbf")
if err := json.Unmarshal(license, &parsedLicense); err != nil {
return nil, errors.WithMessage(err, "failed to parse license")
}
return parsedLicense, nil
}
type client struct {
publicKeys []libtrust.PublicKey
hclient *http.Client
baseURI url.URL
}
// Config holds licensing client configuration
type Config struct {
BaseURI url.URL
HTTPClient *http.Client
// used by licensing client to validate an issued license
PublicKeys []string
}
func errorSummary(body []byte) string {
var be struct {
Message string `json:"message"`
}
jsonErr := json.Unmarshal(body, &be)
if jsonErr != nil {
return clientlib.DefaultErrorSummary(body)
}
return be.Message
}
// New creates a new licensing Client
func New(config *Config) (Client, error) {
publicKeys, err := unmarshalPublicKeys(config.PublicKeys)
if err != nil {
return nil, err
}
hclient := config.HTTPClient
if hclient == nil {
hclient = &http.Client{}
}
return &client{
baseURI: config.BaseURI,
hclient: hclient,
publicKeys: publicKeys,
}, nil
}
func unmarshalPublicKeys(publicKeys []string) ([]libtrust.PublicKey, error) {
trustKeys := make([]libtrust.PublicKey, len(publicKeys))
for i, publicKey := range publicKeys {
trustKey, err := unmarshalPublicKey(publicKey)
if err != nil {
return nil, err
}
trustKeys[i] = trustKey
}
return trustKeys, nil
}
func unmarshalPublicKey(publicKey string) (libtrust.PublicKey, error) {
pemBytes, err := base64.StdEncoding.DecodeString(publicKey)
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"public_key": publicKey,
}, "decode public key failed")
}
key, err := libtrust.UnmarshalPublicKeyPEM(pemBytes)
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"public_key": publicKey,
}, "unmarshal public key failed")
}
return key, nil
}
func (c *client) doReq(ctx context.Context, method string, url *url.URL, opts ...clientlib.RequestOption) (*http.Request, *http.Response, error) {
return clientlib.Do(ctx, method, url.String(), append(c.requestDefaults(), opts...)...)
}
func (c *client) doRequestNoAuth(ctx context.Context, method string, url *url.URL, opts ...clientlib.RequestOption) (*http.Request, *http.Response, error) {
return clientlib.Do(ctx, method, url.String(), append(c.requestDefaults(), opts...)...)
}
func (c *client) requestDefaults() []clientlib.RequestOption {
return []clientlib.RequestOption{
func(req *clientlib.Request) {
tok, _ := jwt.FromContext(req.Context())
req.Header.Add("Authorization", "Bearer "+tok)
req.ErrorSummary = errorSummary
req.Client = c.hclient
},
}
}
func (c *client) StoreLicense(ctx context.Context, dclnt WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error {
return StoreLicense(ctx, dclnt, licenses, localRootDir)
}

View File

@ -1,112 +0,0 @@
// Package errors provides error and error wrapping facilities that allow
// for the easy reporting of call stacks and structured error annotations.
package errors
import (
"bytes"
"encoding/json"
"fmt"
)
// New returns a base error that captures the call stack.
func New(text string) error {
return NewBase(1, text)
}
// Base is an error type that supports capturing the call stack at creation
// time, and storing separate text & data to allow structured logging.
// While it could be used directly, it may make more sense as an
// anonymous inside a package/application specific error struct.
type Base struct {
Text string
Fields Fields
CallStack CallStack
}
// Fields holds the annotations for an error.
type Fields map[string]interface{}
// NewBase creates a new Base, capturing a call trace starting
// at "skip" calls above.
func NewBase(skip int, text string) *Base {
return &Base{
Text: text,
CallStack: CurrentCallStack(skip + 1),
}
}
func (e *Base) Error() string {
return textAndFields(e.Text, e.Fields)
}
// AddFields allows a Error message to be further annotated with
// a set of key,values, to add more context when inspecting
// Error messages.
func (e *Base) AddFields(fields Fields) {
e.Fields = combineFields(e.Fields, fields)
}
// Location returns the location info (file, line, ...) for the place
// where this Error error was created.
func (e *Base) Location() *Location {
return e.CallStack[0].Location()
}
func (e *Base) String() string {
var td string
loc := e.Location()
if e.Text != "" || len(e.Fields) > 0 {
td = fmt.Sprintf(": %s", textAndFields(e.Text, e.Fields))
}
return fmt.Sprintf("%s:%d%s", loc.File, loc.Line, td)
}
// MarshalJSON creates a JSON representation of a Error.
func (e *Base) MarshalJSON() ([]byte, error) {
m := make(Fields)
loc := e.Location()
m["file"] = loc.File
m["line"] = loc.Line
m["func"] = loc.Func
if e.Text != "" {
m["text"] = e.Text
}
if len(e.Fields) > 0 {
m["fields"] = e.Fields
}
return json.Marshal(m)
}
// Stack returns the call stack from where this Error was created.
func (e *Base) Stack() CallStack {
return e.CallStack
}
func combineFields(f1 Fields, f2 Fields) Fields {
data := make(Fields, len(f1)+len(f2))
for k, v := range f1 {
data[k] = v
}
for k, v := range f2 {
data[k] = v
}
return data
}
func textAndFields(text string, fields Fields) string {
buf := &bytes.Buffer{}
if text != "" {
buf.WriteString(text)
}
for k, v := range fields {
buf.WriteByte(' ')
buf.WriteString(k)
buf.WriteByte('=')
fmt.Fprintf(buf, "%+v", v)
}
return string(buf.Bytes())
}

View File

@ -1,101 +0,0 @@
package errors
import "net/http"
// HTTPStatus is a convenience checker for a (possibly wrapped) HTTPStatus
// interface error. If err doesn't support the HTTPStatus interface, it will
// default to either StatusOK or StatusInternalServerError as appropriate.
func HTTPStatus(err error) (status int, ok bool) {
type httperror interface {
HTTPStatus() int
}
_, _, cause := Cause(err)
he, ok := cause.(httperror)
if ok {
return he.HTTPStatus(), true
}
if err == nil {
return http.StatusOK, false
}
return http.StatusInternalServerError, false
}
// HTTPError provides an easy way to specify the http status code equivalent
// for an error at error creation time, as well as inheriting the useful
// features from Base (call stacks, structured text & data).
type HTTPError struct {
*Base
Status int
}
// HTTPStatus returns the appropriate http status code for this error.
func (e *HTTPError) HTTPStatus() int {
if e == nil {
return http.StatusOK
}
if e.Status == 0 {
return http.StatusInternalServerError
}
return e.Status
}
// With allows additional structured data fields to be added to this HTTPError.
func (e *HTTPError) With(fields Fields) *HTTPError {
e.AddFields(fields)
return e
}
// WithStatus allows setting the http status.
func (e *HTTPError) WithStatus(status int) *HTTPError {
e.Status = status
return e
}
// NewHTTPError constructs an error with the given http status.
func NewHTTPError(status int, text string) *HTTPError {
return newHTTPErrorWithDepth(status, text)
}
// newHTTPErrorWithDepth constructs an error with the given http status and
// depth
func newHTTPErrorWithDepth(status int, text string) *HTTPError {
return &HTTPError{
Status: status,
Base: NewBase(2, text),
}
}
// NotFound is returned when a resource was not found.
func NotFound(fields Fields, text string) *HTTPError {
return newHTTPErrorWithDepth(http.StatusNotFound, text).With(fields)
}
// BadRequest is returned when a request did not pass validation, or is
// in appropriate for the state of the resource it would affect.
func BadRequest(fields Fields, text string) *HTTPError {
return newHTTPErrorWithDepth(http.StatusBadRequest, text).With(fields)
}
// Conflict is returned when request could not be completed due to a conflict with
// the current state of the resource.
func Conflict(fields Fields, text string) *HTTPError {
return newHTTPErrorWithDepth(http.StatusConflict, text).With(fields)
}
// PaymentRequired is returned if the requested resource must be purchased.
func PaymentRequired(fields Fields, text string) *HTTPError {
return newHTTPErrorWithDepth(http.StatusPaymentRequired, text).With(fields)
}
// Forbidden is returned if the requesting user does not have the required
// permissions for a request.
func Forbidden(fields Fields, text string) *HTTPError {
return newHTTPErrorWithDepth(http.StatusForbidden, text).With(fields)
}
// InternalError should only be returned if no other specific error applies.
func InternalError(fields Fields, text string) *HTTPError {
return newHTTPErrorWithDepth(http.StatusInternalServerError, text).With(fields)
}

View File

@ -1,65 +0,0 @@
package errors
import (
"encoding/json"
"fmt"
"runtime"
)
// Call holds a call frame
type Call runtime.Frame
// Location is the parsed file, line, and other info we can determine from
// a specific gostack.Call.
type Location struct {
File string
Line int
Func string
}
// Location uses the gostack package to construct file, line, and other
// info about this call.
func (c Call) Location() *Location {
return &Location{
File: c.File,
Line: c.Line,
Func: c.Function,
}
}
// MarshalJSON returns the JSON representation.
func (c Call) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
loc := c.Location()
m["file"] = loc.File
m["line"] = loc.Line
m["func"] = loc.Func
return json.Marshal(m)
}
func (c Call) String() string {
return fmt.Sprintf("%v:%v", c.File, c.Line)
}
// CallStack is a convenience alias for a call stack.
type CallStack []Call
// CurrentCallStack returns the call stack, skipping the specified
// depth of calls.
func CurrentCallStack(skip int) CallStack {
var pcs [128]uintptr
n := runtime.Callers(skip+2, pcs[:])
callersFrames := runtime.CallersFrames(pcs[:n])
cs := make([]Call, 0, n)
for {
frame, more := callersFrames.Next()
cs = append(cs, Call(frame))
if !more {
break
}
}
return cs
}

View File

@ -1,106 +0,0 @@
package errors
import "fmt"
// Wrapf takes an originating "cause" error and annotates
// it with text and the source file & line of the wrap point.
func Wrapf(err error, fields Fields, format string, args ...interface{}) *Wrapped {
w := &Wrapped{
Base: NewBase(1, fmt.Sprintf(format, args...)),
cause: err,
}
w.AddFields(fields)
return w
}
// Wrap takes an originating "cause" error and annotates
// it just with the source file & line of the wrap point.
func Wrap(err error, fields Fields) *Wrapped {
w := &Wrapped{
Base: NewBase(1, err.Error()),
cause: err,
}
w.AddFields(fields)
return w
}
// WithMessage takes an originating cause and text description,
// returning a wrapped error with the text and stack.
func WithMessage(err error, text string) *Wrapped {
return &Wrapped{
Base: NewBase(1, text),
cause: err,
}
}
// WithStack takes an originating cause and returns
// a wrapped error that just records the stack.
func WithStack(err error) *Wrapped {
return &Wrapped{
Base: NewBase(1, err.Error()),
cause: err,
}
}
// Wrapped provides a way to add additional context when passing back
// an error to a caller. It inherits the useful features from Base
// (call stacks, structured text & data).
type Wrapped struct {
*Base
cause error
}
func (w *Wrapped) Error() string {
msg := textAndFields(w.Text, w.Fields)
return msg + ": " + w.cause.Error()
}
// With allows adding additional structured data fields.
func (w *Wrapped) With(fields Fields) *Wrapped {
w.AddFields(fields)
return w
}
// Unwrap extracts any layered Wrapped errors inside of this one,
// returning the first non-Wrapped error found as the original cause.
func (w *Wrapped) Unwrap() (wraps []*Base, cause error) {
for w != nil {
cause = w.cause
wraps = append(wraps, w.Base)
w, _ = w.cause.(*Wrapped)
}
// For consistency, wraps should be in same order as stacks:
// first element is from the innermost wrap
// Hence we need to reverse the append operation above.
for i := len(wraps)/2 - 1; i >= 0; i-- {
j := len(wraps) - 1 - i
wraps[i], wraps[j] = wraps[j], wraps[i]
}
return
}
// Cause checks if the passed in error is a Wrapped. If so, it will
// extract & return information about all the wrapped errors inside.
// It will return the CallStack of cause, if it supports the errors.Stack()
// interface, or the innermost wrap (which should be the closest wrap to cause.)
// If error is not a wrapped, cause is same as input err.
func Cause(err error) (stack CallStack, wraps []*Base, cause error) {
cause = err
if w, ok := err.(*Wrapped); ok {
wraps, cause = w.Unwrap()
}
type stacker interface {
Stack() CallStack
}
if s, ok := cause.(stacker); ok {
stack = s.Stack()
} else {
if len(wraps) > 0 {
stack = wraps[0].Stack()
}
}
return
}

View File

@ -1,21 +0,0 @@
This is a library for handling JWT authentication tokens
### Generating new test certificates
In the event that the test certificates expire, regenerate them with OpenSSL
cd jwt/testdata
Generate new root CA cert and private key
openssl req -newkey rsa:4096 -nodes -keyout root_key.pem -x509 -days 3650 -out root-certs
Generate new intermediate CA cert and private key
openssl req -new -key private-key -out inter.csr
openssl x509 -req -days 3650 -in inter.csr -CA root-certs -CAkey root_key.pem -CAcreateserial -out trusted-cert

View File

@ -1,45 +0,0 @@
package identity
import (
"context"
"fmt"
)
// DockerIdentity identifies a Docker user.
type DockerIdentity struct {
DockerID string
Username string
FullName string
Email string
Scopes []string
}
func (di DockerIdentity) String() string {
return fmt.Sprintf("{docker_id=%v, username=%v, email=%v, scopes=%v}",
di.DockerID, di.Username, di.Email, di.Scopes)
}
// HasScope returns true if the exact input scope is present in the scopes list.
func (di DockerIdentity) HasScope(scope string) bool {
for i := range di.Scopes {
if di.Scopes[i] == scope {
return true
}
}
return false
}
type keyType int
var identityContextKey keyType
// FromContext returns the DockerIdentity value stored in ctx, if any.
func FromContext(ctx context.Context) (*DockerIdentity, bool) {
identity, ok := ctx.Value(identityContextKey).(*DockerIdentity)
return identity, ok
}
// NewContext returns a new Context that carries value identity.
func NewContext(ctx context.Context, identity *DockerIdentity) context.Context {
return context.WithValue(ctx, identityContextKey, identity)
}

View File

@ -1,20 +0,0 @@
package jwt
import (
"context"
)
type key int
var jwtContextKey key
// FromContext returns the token value stored in ctx, if any.
func FromContext(ctx context.Context) (string, bool) {
token, ok := ctx.Value(jwtContextKey).(string)
return token, ok
}
// NewContext returns a new Context that carries value token.
func NewContext(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, jwtContextKey, token)
}

View File

@ -1,284 +0,0 @@
package jwt
import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/docker/licensing/lib/go-auth/identity"
"github.com/google/uuid"
)
const (
// x509 cert chain header field
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#section-4.7
x5c = "x5c"
// non standard username claim
username = "username"
// non standard email claim
email = "email"
// subject claim
// https://tools.ietf.org/html/rfc7519#section-4.1.2
sub = "sub"
// jwt id claim
// https://tools.ietf.org/html/rfc7519#section-4.1.7
jti = "jti"
// issued at claim
// https://tools.ietf.org/html/rfc7519#section-4.1.6
iat = "iat"
// expiration time claim
// https://tools.ietf.org/html/rfc7519#section-4.1.4
exp = "exp"
// Legacy claims the gateways are still using to validate a JWT.
sessionid = "session_id" // same as a `jti`
userid = "user_id" // same as a `sub`
// non standard scope claim
scope = "scope"
)
// EncodeOptions holds JWT encoding options
type EncodeOptions struct {
// The token expiration time, represented as a UNIX timestamp
Expiration int64
// TODO would be good to add a leeway option, but go-jwt
// does not support this. see: https://github.com/dgrijalva/jwt-go/issues/131
// The private key with which to sign the token
SigningKey []byte
// The x509 certificate associated with the signing key
Certificate []byte
// Identifier for the JWT. If this is empty, a random UUID will be generated.
Jti string
// Whether or not to include legacy claims that the gateways are still using to validate a JWT.
IncludeLegacyClaims bool
}
// Encode creates a JWT string for the given identity.DockerIdentity.
func Encode(identity identity.DockerIdentity, options EncodeOptions) (string, error) {
block, _ := pem.Decode(options.Certificate)
if block == nil {
return "", fmt.Errorf("invalid key: failed to parse header")
}
encodedCert := base64.StdEncoding.EncodeToString(block.Bytes)
x5cCerts := [1]string{encodedCert}
// non standard fields
// Note: this is a required field
claims := make(map[string]interface{})
claims[username] = identity.Username
claims[email] = identity.Email
// standard JWT fields, consult the JWT spec for details
claims[sub] = identity.DockerID
if len(identity.Scopes) > 0 {
claims[scope] = strings.Join(identity.Scopes, " ")
}
jtiStr := options.Jti
if len(jtiStr) == 0 {
jtiStr = "jti-" + uuid.New().String()
}
claims[jti] = jtiStr
claims[iat] = time.Now().Unix()
claims[exp] = options.Expiration
if options.IncludeLegacyClaims {
claims[sessionid] = jtiStr
claims[userid] = identity.DockerID
}
// Note: we only support a RS256 signing method right now. If we want to support
// additional signing methods (for example, HS256), this could be specified as an
// encoding option.
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(claims))
token.Header[x5c] = x5cCerts
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(options.SigningKey)
if err != nil {
return "", err
}
return token.SignedString(privateKey)
}
// DecodeOptions holds JWT decoding options
type DecodeOptions struct {
CertificateChain *x509.CertPool
}
// Decode decodes the given JWT string, returning the decoded identity.DockerIdentity
func Decode(tokenStr string, options DecodeOptions) (*identity.DockerIdentity, error) {
token, err := jwt.Parse(tokenStr, keyFunc(options.CertificateChain))
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
return nil, &ValidationError{VError: ve}
}
return nil, fmt.Errorf("error decoding token: %s", err)
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
username, ok := claims[username].(string)
if !ok {
return nil, fmt.Errorf("%v claim not present", username)
}
dockerID, ok := claims[sub].(string)
if !ok {
return nil, fmt.Errorf("%v claim not present", sub)
}
// email is optional
email, _ := claims[email].(string)
var scopes []string
if scopeClaim, ok := claims[scope]; ok {
sstr, ok := scopeClaim.(string)
if !ok {
return nil, fmt.Errorf("scope claim invalid")
}
scopes = strings.Split(sstr, " ")
}
return &identity.DockerIdentity{
Username: username,
DockerID: dockerID,
Email: email,
Scopes: scopes,
}, nil
}
// no error but an invalid token seems like a corner case, but just to be sure
return nil, fmt.Errorf("token was invalid")
}
// IsExpired returns true if the token has expired, false otherwise
func IsExpired(tokenStr string, options DecodeOptions) (bool, error) {
rootCerts := options.CertificateChain
_, err := jwt.Parse(tokenStr, keyFunc(rootCerts))
if err == nil {
return false, nil
}
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&(jwt.ValidationErrorExpired) != 0 {
return true, nil
}
return false, err
}
return false, err
}
// keyFunc returns the jwt.KeyFunc with which to validate the token
func keyFunc(roots *x509.CertPool) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// x5c holds a base64 encoded DER encoded x509 certificate
// associated with the private key used to sign the token.
// For more information, see:
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#page-9
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-B
x5c, ok := token.Header[x5c].([]interface{})
if !ok {
return nil, fmt.Errorf("x5c token header not present")
}
if len(x5c) == 0 {
return nil, fmt.Errorf("x5c token header was empty")
}
x5cString, ok := x5c[0].(string)
if !ok {
return nil, fmt.Errorf("x5c token header was not a string")
}
decodedCert, err := base64.StdEncoding.DecodeString(x5cString)
if err != nil {
return nil, err
}
cert, err := validateCert(decodedCert, roots)
if err != nil {
return nil, err
}
key := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
return jwt.ParseRSAPublicKeyFromPEM(key)
}
}
// validateCert validates the ASN.1 DER encoded cert using the given x509.CertPool root
// certificate chain. If valid, the parsed x509.Certificate is returned.
func validateCert(derData []byte, roots *x509.CertPool) (*x509.Certificate, error) {
opts := x509.VerifyOptions{
Roots: roots,
}
cert, err := x509.ParseCertificate(derData)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: [%v]", err)
}
_, err = cert.Verify(opts)
if err != nil {
return nil, fmt.Errorf("failed to verify certificate: [%v]", err)
}
return cert, nil
}
// ValidationError interrogates the jwt.ValidationError, returning
// a more detailed error message.
type ValidationError struct {
VError *jwt.ValidationError
}
func (e *ValidationError) Error() string {
errs := e.VError.Errors
if errs&jwt.ValidationErrorMalformed != 0 {
return fmt.Sprintf("malformed token error: [%v]", e.VError)
}
if errs&jwt.ValidationErrorUnverifiable != 0 {
return fmt.Sprintf("token signature error: [%v]", e.VError)
}
if errs&jwt.ValidationErrorSignatureInvalid != 0 {
return fmt.Sprintf("token signature error: [%v]", e.VError)
}
if errs&jwt.ValidationErrorExpired != 0 {
return fmt.Sprintf("token expiration error: [%v]", e.VError)
}
if errs&jwt.ValidationErrorNotValidYet != 0 {
return fmt.Sprintf("token NBF validation error: [%v]", e.VError)
}
return fmt.Sprintf("token validation error: [%v]", e.VError)
}

View File

@ -1,5 +0,0 @@
# go-clientlib
## Overview
`go-clientlib` is used to reduce much of the boilerplate needed for sending and receiving http requests and responses in client libraries.

View File

@ -1,305 +0,0 @@
package clientlib
import (
"bytes"
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/docker/licensing/lib/errors"
)
// Do is a shortcut for creating and executing an http request.
func Do(ctx context.Context, method, urlStr string, opts ...RequestOption) (*http.Request, *http.Response, error) {
r, err := New(ctx, method, urlStr, opts...)
if err != nil {
return nil, nil, err
}
res, err := r.Do()
return r.Request, res, err
}
// New creates and returns a new Request, potentially configured via a vector of RequestOption's.
func New(ctx context.Context, method, urlStr string, opts ...RequestOption) (*Request, error) {
req, err := http.NewRequest(method, urlStr, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
r := &Request{
Request: req,
Client: &http.Client{},
ErrorCheck: DefaultErrorCheck,
ErrorBodyMaxLength: defaultErrBodyMaxLength,
ErrorSummary: DefaultErrorSummary,
RequestPrepare: DefaultRequestPrepare,
ResponseHandle: DefaultResponseHandle,
}
for _, o := range opts {
o(r)
}
return r, nil
}
// Request encompasses an http.Request, plus configured behavior options.
type Request struct {
*http.Request
Client *http.Client
ErrorCheck ErrorCheck
ErrorBodyMaxLength int64
ErrorSummary ErrorSummary
ResponseHandle ResponseHandle
RequestPrepare RequestPrepare
}
// Do executes the Request. The Request.ErrorCheck to determine
// if this attempt has failed, and transform the returned error.
// Otherwise, Request.ResponseHandler will examine the response.
// It's expected that the ResponseHandler has been configured via
// a RequestOption to perform response parsing and storing.
func (r *Request) Do() (*http.Response, error) {
err := r.RequestPrepare(r)
if err != nil {
return nil, err
}
res, err := r.Client.Do(r.Request)
err = r.ErrorCheck(r, err, res)
if err != nil {
return res, err
}
return res, r.ResponseHandle(r, res)
}
// SetBody mirrors the ReadCloser config in http.NewRequest,
// ensuring that a ReadCloser is used for http.Request.Body.
func (r *Request) SetBody(body io.Reader) {
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = ioutil.NopCloser(body)
}
r.Body = rc
}
// ErrorFields returns error annotation fields for the request.
func (r *Request) ErrorFields() map[string]interface{} {
return map[string]interface{}{
"url": r.URL.String(),
"method": r.Method,
}
}
// ErrorCheck is the signature for the function that is passed
// the error & response immediately from http.Client.Do().
type ErrorCheck func(r *Request, doErr error, res *http.Response) error
// DefaultErrorCheck is the default error checker used if none is
// configured on a Request. doErr and res are the return values of
// executing http.Client.Do(), so any implementation should first
// check doErr for non-nill & react appropriately. If an http response
// was received, if a non-200 class status was also received, then
// the response body will be read (up to a const limit) and passed
// to request.ErrorSummary to attempt to parse out the error body,
// which will be passed as the "detail" flag on the returned error.
func DefaultErrorCheck(r *Request, doErr error, res *http.Response) error {
if doErr != nil {
return errors.Wrap(doErr, r.ErrorFields())
}
status := res.StatusCode
if status >= 200 && status < 300 {
return nil
}
defer res.Body.Close()
body, _ := ioutil.ReadAll(io.LimitReader(res.Body, r.ErrorBodyMaxLength))
detail := r.ErrorSummary(body)
message := fmt.Sprintf("%s %s returned %d : %s", r.Method, r.URL.String(), status, detail)
return errors.NewHTTPError(status, message).
With(r.ErrorFields()).
With(map[string]interface{}{
"http_status": status,
"detail": detail,
})
}
// Default error response max length, in bytes
const defaultErrBodyMaxLength = 256
// ErrorSummary is the signature for the function that is passed
// the fully read body of an error response.
type ErrorSummary func([]byte) string
// DefaultErrorSummary just returns the string of the received
// error body. Note that the body passed in is potentially truncated
// before this call.
func DefaultErrorSummary(body []byte) string {
return string(body)
}
// RequestPrepare is the signature for the function called
// before calling http.Client.Do, to perform any preparation
// needed before executing the request, eg. marshaling the body.
type RequestPrepare func(r *Request) error
// DefaultRequestPrepare does nothing.
func DefaultRequestPrepare(*Request) error {
return nil
}
// ResponseHandle is the signature for the function called if
// ErrorCheck returns a nil error, and is responsible for performing
// any reads or stores from the request & response.
type ResponseHandle func(*Request, *http.Response) error
// DefaultResponseHandle merely closes the response body.
func DefaultResponseHandle(r *Request, res *http.Response) error {
res.Body.Close()
return nil
}
// RequestOption is the signature for functions that can perform
// some configuration of a Request.
type RequestOption func(*Request)
// SendJSON returns a RequestOption that will marshal
// and set the json body & headers on a request.
func SendJSON(sends interface{}) RequestOption {
return func(r *Request) {
r.Header.Set("Content-Type", "application/json")
r.RequestPrepare = func(r *Request) error {
bits, err := json.Marshal(sends)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
body := bytes.NewReader(bits)
r.SetBody(body)
r.ContentLength = int64(body.Len())
return nil
}
}
}
// RecvJSON returns a RequestOption that will set the json headers
// on a request, and set a ResponseHandler that will unmarshal
// the response body to the given interface{}.
func RecvJSON(recvs interface{}) RequestOption {
return func(r *Request) {
r.Header.Set("Accept", "application/json")
r.Header.Set("Accept-Charset", "utf-8")
r.ResponseHandle = func(r *Request, res *http.Response) error {
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
err = json.Unmarshal(body, recvs)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
return nil
}
}
}
// SendXML returns a RequestOption that will marshal
// and set the xml body & headers on a request.
func SendXML(sends interface{}) RequestOption {
return func(r *Request) {
r.Header.Set("Content-Type", "application/xml")
r.RequestPrepare = func(r *Request) error {
bits, err := xml.Marshal(sends)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
body := bytes.NewReader(bits)
r.SetBody(body)
r.ContentLength = int64(body.Len())
return nil
}
}
}
// RecvXML returns a RequestOption that will set the xml headers
// on a request, and set a ResponseHandler that will unmarshal
// the response body to the given interface{}.
func RecvXML(recvs interface{}) RequestOption {
return func(r *Request) {
r.Header.Set("Accept", "application/xml")
r.Header.Set("Accept-Charset", "utf-8")
r.ResponseHandle = func(r *Request, res *http.Response) error {
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
err = xml.Unmarshal(body, recvs)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
return nil
}
}
}
// SendText returns a RequestOption that will marshal
// and set the text body & headers on a request.
func SendText(sends string) RequestOption {
return func(r *Request) {
r.Header.Set("Content-Type", "text/plain")
r.RequestPrepare = func(r *Request) error {
body := strings.NewReader(sends)
r.SetBody(body)
r.ContentLength = int64(body.Len())
return nil
}
}
}
// RecvText returns a RequestOption that will set the text headers
// on a request, and set a ResponseHandler that will unmarshal
// the response body to the given string.
func RecvText(recvs *string) RequestOption {
return func(r *Request) {
r.Header.Set("Accept", "text/plain")
r.Header.Set("Accept-Charset", "utf-8")
r.ResponseHandle = func(r *Request, res *http.Response) error {
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return errors.Wrap(err, r.ErrorFields())
}
sbody := string(body)
*recvs = sbody
return nil
}
}
}
// DontClose sets the ResponseBody to an empty function, so that the
// response body is not automatically closed. Users of this should be
// sure to call res.Body.Close().
func DontClose() RequestOption {
return func(r *Request) {
r.ResponseHandle = func(*Request, *http.Response) error {
return nil
}
}
}

View File

@ -1,5 +0,0 @@
# go-validation
## Overview
`go-validation` provides struct validation utilities. See `validation_test.go` for sample usage.

View File

@ -1,141 +0,0 @@
package validation
import (
"fmt"
"reflect"
validator "github.com/asaskevich/govalidator"
)
// ErrorKind represents a validation error type
type ErrorKind int
// ErrorKind enumeration
const (
// ErrorGeneral represents a catchall validation error. That is,
// an error that does not match any ErrorKind.
ErrorGeneral ErrorKind = iota
// ErrorEmpty represents an empty field validation error.
ErrorEmpty
// ErrorInvalidEmail represents an invalid email validation error.
ErrorInvalidEmail
// ErrorInvalidURL represents an invalid url validation error.
ErrorInvalidURL
)
// Error represents a validation error
type Error struct {
FieldName string
FieldValue interface{}
kind ErrorKind
}
// Error returns the string representation of the error
func (e *Error) Error() string {
msg := e.MsgForCode()
return fmt.Sprintf("%v invalid: %v", e.FieldName, msg)
}
// MsgForCode returns a human readable message for the given ErrorKind.
// The message may optionally include the given field value in the message.
func (e *Error) MsgForCode() string {
switch e.kind {
case ErrorEmpty:
return "not provided"
case ErrorInvalidEmail:
return fmt.Sprintf("%v is an invalid email address", e.FieldValue)
case ErrorInvalidURL:
return fmt.Sprintf("%v is an invalid url", e.FieldValue)
}
return fmt.Sprintf("'%v' is not a valid value", e.FieldValue)
}
// Errors is a list of validation Errors
type Errors []*Error
// Validater has a single function Validate, which can be used to determine
// if the interface implementation is valid. Validate should return true if the implementation
// passes validation, false otherwise. If invalid, a list of one or more validation
// Errors should be returned.
type Validater interface {
Validate() (bool, Errors)
}
// IsEmpty returns true if the given interface is empty, false otherwise
func IsEmpty(s interface{}) bool {
v := reflect.ValueOf(s)
switch v.Kind() {
case reflect.String,
reflect.Array:
return v.Len() == 0
case reflect.Map,
reflect.Slice:
return v.Len() == 0 || v.IsNil()
case reflect.Bool:
return !v.Bool()
case reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64:
return v.Int() == 0
case reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32,
reflect.Float64:
return v.Float() == 0
case reflect.Interface,
reflect.Ptr:
return v.IsNil()
}
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}
// IsEmail returns true if the given email address is valid, false otherwise
func IsEmail(str string) bool {
return validator.IsEmail(str)
}
// IsURL returns true if the given url is valid, false otherwise
func IsURL(str string) bool {
return validator.IsURL(str)
}
// Matches returns true if the given string matches the given pattern, false otherwise
func Matches(str, pattern string) bool {
return validator.Matches(str, pattern)
}
// InvalidEmpty returns an empty validation Error for the given field name
func InvalidEmpty(fieldName string) *Error {
return &Error{
FieldName: fieldName,
kind: ErrorEmpty,
}
}
// InvalidEmail returns an invalid validation error for the given field name and value
func InvalidEmail(fieldName, fieldValue string) *Error {
return &Error{
FieldName: fieldName,
FieldValue: fieldValue,
kind: ErrorInvalidEmail,
}
}
// InvalidURL returns an invalid validation error for the given field name and value
func InvalidURL(fieldName, fieldValue string) *Error {
return &Error{
FieldName: fieldName,
FieldValue: fieldValue,
kind: ErrorInvalidURL,
}
}

View File

@ -1,158 +0,0 @@
package licensing
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"time"
"github.com/docker/libtrust"
"github.com/docker/licensing/lib/errors"
"github.com/docker/licensing/lib/go-clientlib"
"github.com/docker/licensing/model"
)
func (c *client) getLicenseFile(ctx context.Context, subID string) (*model.IssuedLicense, error) {
url := c.baseURI
url.Path += fmt.Sprintf("/api/billing/v4/subscriptions/%s/license-file", subID)
license := new(model.IssuedLicense)
if _, _, err := c.doReq(ctx, "GET", &url, clientlib.RecvJSON(license)); err != nil {
return nil, err
}
return license, nil
}
// Check verifies that the license identified by the given key id is valid. Note that it does not
// interrogate the contents of the license.
func (c *client) check(ctx context.Context, license model.IssuedLicense) (*model.CheckResponse, error) {
keyID := license.KeyID
privateKey := license.PrivateKey
authorization, err := c.getAuthorization(ctx, license)
if err != nil {
return nil, err
}
// TODO: Mason - replace this parseJWS with a non libtrust lib
signature, err := libtrust.ParseJWS(authorization)
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"key_id": keyID,
}, "license parse JWS failed")
}
keys, err := signature.Verify()
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"key_id": keyID,
}, "license signature verification failed")
}
keyCnt := len(keys)
if keyCnt != 1 {
err = fmt.Errorf("unexpected number of signing keys (%d)", keyCnt)
return nil, errors.WithStack(err).With(errors.Fields{
"key_id": keyID,
})
}
key := keys[0]
if !c.recognizedSigningKey(key) {
return nil, errors.New("unrecognized signing key")
}
payload, err := signature.Payload()
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"key_id": keyID,
}, "malformed signature payload")
}
checkRes := new(model.CheckResponse)
err = json.Unmarshal(payload, &checkRes)
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"key_id": keyID,
}, "license payload unmarshal failed")
}
msg := checkRes.Expiration.Format(time.RFC3339)
if err := checkToken(msg, checkRes.Token, privateKey); err != nil {
return nil, errors.Wrap(err, errors.Fields{
"key_id": keyID,
})
}
return checkRes, nil
}
// recognizedSigningKey returns true if the given key is signed with a recognized signing key, false otherwise
func (c *client) recognizedSigningKey(key libtrust.PublicKey) bool {
for _, publicKey := range c.publicKeys {
if key.KeyID() == publicKey.KeyID() {
return true
}
}
return false
}
// getAuthorization returns the decoded license authorization
func (c *client) getAuthorization(ctx context.Context, license model.IssuedLicense) ([]byte, error) {
decoded, err := base64.StdEncoding.DecodeString(license.Authorization)
if err != nil {
return nil, errors.Wrapf(err, errors.Fields{
"key_id": license.KeyID,
}, "decoding license authorization failed")
}
return decoded, nil
}
// All of the functions in this file assume that they are receiving a properly
// formatted private key.
// checkToken performs a MAC algorithm (where token is generated by hashing the
// message with the privateKey via GenerateToken) with the purpose of authenticating
// the validity of both the message and the private key of the person who generated
// the token.
func checkToken(message, token, privateKey string) error {
tokenBytes, err := base64.URLEncoding.DecodeString(token)
if err != nil {
return errors.Wrap(err, errors.Fields{"token": token})
}
generatedToken, err := generateToken(message, privateKey)
if err != nil {
return errors.Wrap(err, errors.Fields{"token": token})
}
generatedBytes, err := base64.URLEncoding.DecodeString(generatedToken)
if err != nil {
return errors.Wrap(err, errors.Fields{"token": token})
}
if !hmac.Equal(tokenBytes, generatedBytes) {
return errors.Forbidden(errors.Fields{"token": token}, "invalid token")
}
return nil
}
// generateToken generates a hash of the message with the privateKey via the
// sha256 algorithm.
func generateToken(message, privateKey string) (string, error) {
key, err := base64.URLEncoding.DecodeString(privateKey)
if err != nil {
return "", errors.Wrap(err, errors.Fields{"msg": message})
}
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
return base64.URLEncoding.EncodeToString(h.Sum(nil)), nil
}

View File

@ -1,53 +0,0 @@
package model
import "time"
// A CheckResponse is the internal content of the PublicCheckResponse signed
// json blob.
type CheckResponse struct {
Expiration time.Time `json:"expiration"`
Token string `json:"token"`
MaxEngines int `json:"maxEngines"`
ScanningEnabled bool `json:"scanningEnabled"`
Type string `json:"licenseType"`
Tier string `json:"tier"`
SubscriptionID string `json:"subscription_id,omitempty"`
ProductID string `json:"product_id,omitempty"`
RatePlanID string `json:"rate_plan_id,omitempty"`
Version int `json:"version"`
GraceDays int `json:"grace_days,omitempty"`
Metadata *Metadata `json:"metadata,omitempty"`
PricingComponents PricingComponents `json:"pricing_components,omitempty"`
}
// Metadata holds non-essential license information, that is, anything that is not required by clients to ensure
// the license is valid
type Metadata struct {
Username string `json:"username,omitempty"`
Company string `json:"company,omitempty"`
}
// IssuedLicense represents an issued license
type IssuedLicense struct {
KeyID string `json:"key_id"`
PrivateKey string `json:"private_key"`
Authorization string `json:"authorization"`
}
// Valid returns true if the License is syntactically valid, false otherwise
func (l *IssuedLicense) Valid() (bool, string) {
if l.KeyID == "" {
return false, "empty key_id"
}
if l.PrivateKey == "" {
return false, "empty private_key"
}
if l.Authorization == "" {
return false, "empty authorization"
}
return true, ""
}

View File

@ -1,202 +0,0 @@
package model
import (
"fmt"
"strings"
"time"
validation "github.com/docker/licensing/lib/go-validation"
"github.com/docker/licensing/types"
)
// PricingComponents represents a collection of pricing components
type PricingComponents []*SubscriptionPricingComponent
func (comps PricingComponents) Len() int { return len(comps) }
func (comps PricingComponents) Swap(i, j int) { comps[i], comps[j] = comps[j], comps[i] }
// always sorting by name
func (comps PricingComponents) Less(i, j int) bool { return comps[i].Name < comps[j].Name }
// Subscription includes the base fields that will be meaningful to most of the clients consuming this api
type Subscription struct {
Name string `json:"name"`
ID string `json:"subscription_id"`
DockerID string `json:"docker_id"`
ProductID string `json:"product_id"`
ProductRatePlan string `json:"product_rate_plan"`
ProductRatePlanID string `json:"product_rate_plan_id"`
Start *time.Time `json:"current_period_start,omitempty"`
Expires *time.Time `json:"current_period_end,omitempty"`
State string `json:"state"`
Eusa *EusaState `json:"eusa,omitempty"`
PricingComponents PricingComponents `json:"pricing_components"`
GraceDays int `json:"grace_days"`
}
// GetFeatureValue returns true if a given feature is among a subscription's pricing component entitlements along with
// it's corresponding value and false if it is not found
func (s *Subscription) GetFeatureValue(featureName string) (int, bool) {
for _, component := range s.PricingComponents {
if component.Name == featureName && component.Value > 0 {
return component.Value, true
}
}
return 0, false
}
func (s *Subscription) String() string {
storeURL := "https://docker.com/licensing"
var nameMsg, expirationMsg, statusMsg string
switch types.State(s.State) {
case types.Cancelled:
statusMsg = fmt.Sprintf("\tCancelled! You will no longer receive updates. To purchase go to %s", storeURL)
expirationMsg = fmt.Sprintf("Expiration date: %s", s.Expires.Format("2006-01-02"))
case types.Expired:
statusMsg = fmt.Sprintf("\tExpired! You will no longer receive updates. Please renew at %s", storeURL)
expirationMsg = fmt.Sprintf("Expiration date: %s", s.Expires.Format("2006-01-02"))
case types.Preparing:
statusMsg = "\tYour subscription has not yet begun"
expirationMsg = fmt.Sprintf("Activation date: %s", s.Start.Format("2006-01-02"))
case types.Failed:
statusMsg = "\tOops, this subscription did not get setup properly!"
expirationMsg = ""
case types.Active:
statusMsg = "\tLicense is currently active"
expirationMsg = fmt.Sprintf("Expiration date: %s", s.Expires.Format("2006-01-02"))
default:
expirationMsg = fmt.Sprintf("Expiration date: %s", s.Expires.Format("2006-01-02"))
}
pcStrs := make([]string, len(s.PricingComponents))
for i, pc := range s.PricingComponents {
pcStrs[i] = fmt.Sprintf("%d %s", pc.Value, pc.Name)
}
componentsMsg := "Components: " + strings.Join(pcStrs, ", ")
if s.Name != "" {
nameMsg = fmt.Sprintf("License Name: %s\t", s.Name)
} else if s.ProductRatePlan == "free-trial" {
// TODO - consider a humanized formatting for expiration time on trials (e.g., "10 days remaining")
nameMsg = "Free trial\t"
statusMsg = fmt.Sprintf("\tTo purchase go to %s", storeURL)
}
return fmt.Sprintf("%s%s\t%s%s", nameMsg, componentsMsg, expirationMsg, statusMsg)
}
// SubscriptionDetail presents Subscription information to billing service clients.
type SubscriptionDetail struct {
Subscription
Origin string `json:"origin,omitempty"`
OrderID string `json:"order_id,omitempty"`
OrderItemID string `json:"order_item_id,omitempty"`
InitialPeriodStart time.Time `json:"initial_period_start"`
CreatedByID string `json:"created_by_docker_id"`
// If true, the product for this subscription uses product keys. To
// obtain the keys, the frontend or billing client will need to
// make additional calls to the fulfillment service.
UsesProductKeys bool `json:"uses_product_keys,omitempty"`
// If non-empty, this is a managed subscription, and this identifier is
// known to the fulfillment service as a means to uniquely identify the
// partner that manages this subscription.
//
// Different permissions checking will be used to authorize changes and
// cancellation; the entity entitled to this subscription (represented
// by DockerID) may not change or cancel it directly.
ManagingPartnerID string `json:"managing_partner_id,omitempty"`
// If non-empty, this is a managed subscription, and this ID belongs to the
// account of a user within a partner's account system.
PartnerAccountID string `json:"partner_account_id,omitempty"`
// Marketing opt-in for the subscription. This means customer agrees to receive additional marketing emails
MarketingOptIn bool `json:"marketing_opt_in"`
}
// SubscriptionPricingComponent captures pricing component values that have been selected by the user.
type SubscriptionPricingComponent struct {
Name string `json:"name"`
Value int `json:"value"`
}
// SubscriptionCreationRequest represents a subscription creation request
type SubscriptionCreationRequest struct {
Name string `json:"name"`
DockerID string `json:"docker_id"`
ProductID string `json:"product_id"`
ProductRatePlan string `json:"product_rate_plan"`
Eusa *EusaState `json:"eusa,omitempty"`
Origin string `json:"origin,omitempty"`
OrderID string `json:"order_id,omitempty"`
OrderItemID string `json:"order_item_id,omitempty"`
End *time.Time `json:"end,omitempty"`
Start *time.Time `json:"start,omitempty"`
CouponCodes []string `json:"coupon_codes"`
PricingComponents PricingComponents `json:"pricing_components"`
// If true, the product for this subscription uses product keys. To
// obtain the keys, the frontend or billing client will need to
// make additional calls to the fulfillment service.
UsesProductKeys bool `json:"uses_product_keys,omitempty"`
// Should be non-empty only if creating a managed subscription that will
// be controlled by a partner or publisher. This identifier matches
// whatever the fulfillment service uses as guid's for partners.
ManagingPartnerID string `json:"managing_partner_id,omitempty"`
// Should be non-empty only if creating a managed subscription on behalf
// of a partner, and this ID represent's a partner's user's account id.
PartnerAccountID string `json:"partner_account_id,omitempty"`
// Marketing opt-in for the subscription. This means customer agrees to receive additional marketing emails
MarketingOptIn bool `json:"marketing_opt_in"`
}
// Validate returns true if the subscription request is valid, false otherwise.
// If invalid, one or more validation Errors will be returned.
func (s *SubscriptionCreationRequest) Validate() (bool, validation.Errors) {
var errs validation.Errors
if validation.IsEmpty(s.Name) {
errs = append(errs, validation.InvalidEmpty("name"))
}
if validation.IsEmpty(s.DockerID) {
errs = append(errs, validation.InvalidEmpty("docker_id"))
}
if validation.IsEmpty(s.ProductID) {
errs = append(errs, validation.InvalidEmpty("product_id"))
}
if validation.IsEmpty(s.ProductRatePlan) {
errs = append(errs, validation.InvalidEmpty("product_rate_plan"))
}
for i, component := range s.PricingComponents {
if validation.IsEmpty(component.Name) {
name := fmt.Sprintf("pricing_component[%v]/name", i)
errs = append(errs, validation.InvalidEmpty(name))
}
}
valid := len(errs) == 0
return valid, errs
}
// EusaState encodes whether the subscription's EUSA has been accepted,
// and if so, by whom and when.
// See json marshal & unmarshal below.
type EusaState struct {
Accepted bool `json:"accepted"`
AcceptedBy string `json:"accepted_by,omitempty"`
AcceptedOn string `json:"accepted_on,omitempty"`
}

View File

@ -1,91 +0,0 @@
package model
import (
"fmt"
"time"
"github.com/docker/licensing/lib/errors"
)
// User details a Docker user
type User struct {
ID string `json:"id"`
Username string `json:"username"`
DateJoined time.Time `json:"date_joined"`
// The user type. Is either 'User' or 'Organization'
Type string `json:"type"`
FullName string `json:"full_name,omitempty"`
Location string `json:"location,omitempty"`
Company string `json:"company,omitempty"`
ProfileURL string `json:"profile_url,omitempty"`
GravatarURL string `json:"gravatar_url,omitempty"`
}
// Org details a Docker organization
type Org struct {
ID string `json:"id"`
Orgname string `json:"orgname"`
DateJoined time.Time `json:"date_joined"`
Type string `json:"type"`
FullName string `json:"full_name,omitempty"`
Location string `json:"location,omitempty"`
Company string `json:"company,omitempty"`
ProfileURL string `json:"profile_url,omitempty"`
GravatarURL string `json:"gravatar_url,omitempty"`
}
// PaginationParams is used for specifying pagination in requests to accounts
type PaginationParams struct {
PageSize int
Page int
}
// PaginatedMeta describes fields contained in a paginated response body
type PaginatedMeta struct {
Count int `json:"count"`
PageSize int `json:"page_size,omitempty"`
Next *string `json:"next,omitempty"`
Previous *string `json:"previous,omitempty"`
}
// LoginResult holds the response of the login endpoint
type LoginResult struct {
// JWT associated with the authenticated user
Token string `json:"token"`
}
// LoginRequest holds a hub user's username and password
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
// LoginError wraps both the http error and raw hub login error
type LoginError struct {
*errors.HTTPError
// Raw is the raw error from Accounts service
Raw *RawLoginError
}
var _ error = (*LoginError)(nil)
func (e *LoginError) Error() string {
msg := e.HTTPError.Error()
if e.Raw != nil {
msg = fmt.Sprintf("%s (raw: %+v)", msg, e.Raw)
}
return msg
}
// RawLoginError is the raw format of errors returned from the Accounts service.
type RawLoginError struct {
Detail string `json:"detail,omitempty"`
// These fields wil be populated if it's a validation error
Username []string `json:"username,omitempty"`
Password []string `json:"password,omitempty"`
}

View File

@ -1,205 +0,0 @@
package licensing
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/licensing/model"
)
var (
licenseNamePrefix = "com.docker.license"
licenseFilename = "docker.lic"
// ErrWorkerNode returned on a swarm worker node - lookup licenses on swarm managers
ErrWorkerNode = fmt.Errorf("this node is not a swarm manager - check license status on a manager node")
// ErrUnlicensed returned when no license found
ErrUnlicensed = fmt.Errorf("no license found")
)
// WrappedDockerClient provides methods useful for installing licenses to the wrapped docker engine or cluster
type WrappedDockerClient interface {
Info(ctx context.Context) (types.Info, error)
NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error)
ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error)
ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error)
}
// StoreLicense will store the license on the host filesystem and swarm (if swarm is active)
func StoreLicense(ctx context.Context, clnt WrappedDockerClient, license *model.IssuedLicense, rootDir string) error {
licenseData, err := json.Marshal(*license)
if err != nil {
return err
}
// First determine if we're in swarm-mode or a stand-alone engine
_, err = clnt.NodeList(ctx, types.NodeListOptions{})
if err != nil { // TODO - check for the specific error message
return writeLicenseToHost(ctx, clnt, licenseData, rootDir)
}
// Load this in the latest license index
latestVersion, err := getLatestNamedConfig(clnt, licenseNamePrefix)
if err != nil {
return fmt.Errorf("unable to get latest license version: %s", err)
}
spec := swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: fmt.Sprintf("%s-%d", licenseNamePrefix, latestVersion+1),
Labels: map[string]string{
"com.docker.ucp.access.label": "/",
"com.docker.ucp.collection": "swarm",
"com.docker.ucp.collection.root": "true",
"com.docker.ucp.collection.swarm": "true",
},
},
Data: licenseData,
}
_, err = clnt.ConfigCreate(context.Background(), spec)
if err != nil {
return fmt.Errorf("Failed to create license: %s", err)
}
return nil
}
func (c *client) LoadLocalLicense(ctx context.Context, clnt WrappedDockerClient) (*model.Subscription, error) {
info, err := clnt.Info(ctx)
if err != nil {
return nil, err
}
var licenseData []byte
if info.Swarm.LocalNodeState != "active" {
licenseData, err = readLicenseFromHost(ctx, info.DockerRootDir)
} else {
// Load the latest license index
var latestVersion int
// check if node is swarm manager
if !info.Swarm.ControlAvailable {
return nil, ErrWorkerNode
}
latestVersion, err = getLatestNamedConfig(clnt, licenseNamePrefix)
if err != nil {
return nil, fmt.Errorf("unable to get latest license version: %s", err)
}
if latestVersion >= 0 {
cfg, _, err := clnt.ConfigInspectWithRaw(ctx, fmt.Sprintf("%s-%d", licenseNamePrefix, latestVersion))
if err != nil {
return nil, fmt.Errorf("unable to load license from swarm config: %s", err)
}
licenseData = cfg.Spec.Data
} else {
licenseData, err = readLicenseFromHost(ctx, info.DockerRootDir)
}
}
if err != nil {
if os.IsNotExist(err) {
return nil, ErrUnlicensed
}
return nil, fmt.Errorf("Failed to create license: %s", err)
}
parsedLicense, err := c.ParseLicense(licenseData)
if err != nil {
return nil, err
}
checkResponse, err := c.VerifyLicense(ctx, *parsedLicense)
if err != nil {
return nil, err
}
return checkResponseToSubscription(checkResponse), nil
}
func checkResponseToSubscription(checkResponse *model.CheckResponse) *model.Subscription {
// Determine if the license has already expired
var state string
if checkResponse.Expiration.Before(time.Now()) {
state = "expired"
} else {
state = "active"
}
// For backward compatibility, show information about old (per node) licenses
components := checkResponse.PricingComponents
if checkResponse.MaxEngines > 0 {
components = append(components, &model.SubscriptionPricingComponent{
Name: "Nodes",
Value: checkResponse.MaxEngines,
})
}
// Translate the legacy structure into the new Subscription fields
return &model.Subscription{
ID: checkResponse.SubscriptionID,
ProductID: checkResponse.ProductID,
ProductRatePlanID: checkResponse.RatePlanID,
Expires: &checkResponse.Expiration,
State: state,
PricingComponents: components,
GraceDays: checkResponse.GraceDays,
}
}
func (c *client) SummarizeLicense(checkResponse *model.CheckResponse) *model.Subscription {
return checkResponseToSubscription(checkResponse)
}
// getLatestNamedConfig looks for versioned instances of configs with the
// given name prefix which have a `-NUM` integer version suffix. Returns the
// config with the higest version number found or nil if no such configs exist
// along with its version number.
func getLatestNamedConfig(dclient WrappedDockerClient, namePrefix string) (int, error) {
latestVersion := -1
// List any/all existing configs so that we create a newer version than
// any that already exist.
filter := filters.NewArgs()
filter.Add("name", namePrefix)
existingConfigs, err := dclient.ConfigList(context.Background(), types.ConfigListOptions{Filters: filter})
if err != nil {
return latestVersion, fmt.Errorf("unable to list existing configs: %s", err)
}
for _, existingConfig := range existingConfigs {
existingConfigName := existingConfig.Spec.Name
nameSuffix := strings.TrimPrefix(existingConfigName, namePrefix)
if nameSuffix == "" || nameSuffix[0] != '-' {
continue // No version specifier?
}
versionSuffix := nameSuffix[1:] // Trim the version separator.
existingVersion, err := strconv.Atoi(versionSuffix)
if err != nil {
continue // Unable to parse version as integer.
}
if existingVersion > latestVersion {
latestVersion = existingVersion
}
}
return latestVersion, nil
}
func writeLicenseToHost(ctx context.Context, dclient WrappedDockerClient, license []byte, rootDir string) error {
// TODO we should write the file out over the clnt instead of to the local filesystem
return ioutil.WriteFile(filepath.Join(rootDir, licenseFilename), license, 0644)
}
func readLicenseFromHost(ctx context.Context, rootDir string) ([]byte, error) {
// TODO we should read the file in over the clnt instead of to the local filesystem
return ioutil.ReadFile(filepath.Join(rootDir, licenseFilename))
}

View File

@ -1,75 +0,0 @@
package licensing
import (
"context"
"net/url"
"github.com/docker/licensing/lib/go-clientlib"
"github.com/docker/licensing/model"
)
// RequestParams holds request parameters
type RequestParams struct {
DockerID string
PartnerAccountID string
Origin string
}
func (c *client) createSubscription(ctx context.Context, request *model.SubscriptionCreationRequest) (*model.SubscriptionDetail, error) {
url := c.baseURI
url.Path += "/api/billing/v4/subscriptions"
response := new(model.SubscriptionDetail)
if _, _, err := c.doReq(ctx, "POST", &url, clientlib.SendJSON(request), clientlib.RecvJSON(response)); err != nil {
return nil, err
}
return response, nil
}
func (c *client) getSubscription(ctx context.Context, id string) (*model.SubscriptionDetail, error) {
url := c.baseURI
url.Path += "/api/billing/v4/subscriptions/" + id
response := new(model.SubscriptionDetail)
if _, _, err := c.doReq(ctx, "GET", &url, clientlib.RecvJSON(response)); err != nil {
return nil, err
}
return response, nil
}
func (c *client) listSubscriptions(ctx context.Context, params map[string]string) ([]*model.Subscription, error) {
values := url.Values{}
values.Set("docker_id", params["docker_id"])
values.Set("partner_account_id", params["partner_account_id"])
values.Set("origin", params["origin"])
values.Set("include_orgs", "true")
url := c.baseURI
url.Path += "/api/billing/v4/subscriptions"
url.RawQuery = values.Encode()
response := make([]*model.Subscription, 0)
if _, _, err := c.doReq(ctx, "GET", &url, clientlib.RecvJSON(&response)); err != nil {
return nil, err
}
return response, nil
}
func (c *client) listSubscriptionsDetails(ctx context.Context, params map[string]string) ([]*model.SubscriptionDetail, error) {
values := url.Values{}
values.Set("docker_id", params["docker_id"])
values.Set("partner_account_id", params["partner_account_id"])
values.Set("origin", params["origin"])
url := c.baseURI
url.Path += "/api/billing/v4/subscriptions"
url.RawQuery = values.Encode()
response := make([]*model.SubscriptionDetail, 0)
if _, _, err := c.doReq(ctx, "GET", &url, clientlib.RecvJSON(&response)); err != nil {
return nil, err
}
return response, nil
}

View File

@ -1,17 +0,0 @@
package types
// State represents a given subscription's current status
type State string
const (
// Active means a subscription is currently in a working, live state
Active State = "active"
// Expired means a subscription's end date is in the past
Expired State = "expired"
// Cancelled means the subscription has been cancelled
Cancelled State = "cancelled"
// Preparing means that the subscription's payment (if any) is being still processed
Preparing State = "preparing"
// Failed means that there was a problem creating the subscription
Failed State = "failed"
)

View File

@ -1,90 +0,0 @@
package licensing
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/docker/licensing/lib/errors"
"github.com/docker/licensing/lib/go-clientlib"
"github.com/docker/licensing/model"
)
func (c *client) getUserByName(ctx context.Context, username string) (*model.User, error) {
url := c.baseURI
url.Path += fmt.Sprintf("/v2/users/%s/", username)
response := new(model.User)
_, _, err := c.doRequestNoAuth(ctx, "GET", &url, clientlib.RecvJSON(response))
return response, err
}
func (c *client) getUserOrgs(ctx context.Context, params model.PaginationParams) ([]model.Org, error) {
values := url.Values{}
if params.PageSize != 0 {
values.Set("page_size",
fmt.Sprintf("%v", params.PageSize))
}
if params.Page != 0 {
values.Set("page",
fmt.Sprintf("%v", params.Page))
}
requrl := c.baseURI
requrl.Path = "/v2/user/orgs/"
requrl.RawQuery = values.Encode()
var response struct {
model.PaginatedMeta
Results []model.Org `json:"results"`
}
_, _, err := c.doReq(ctx, "GET", &requrl, clientlib.RecvJSON(&response))
return response.Results, err
}
// login calls the login endpoint
// If an error is returned by the Accounts service (as opposed to connection error, json unmarshalling error etc.),
// `error` will be of type `LoginError`
func (c *client) login(ctx context.Context, username string, password string) (*model.LoginResult, error) {
url := c.baseURI
url.Path += "/v2/users/login/"
request := model.LoginRequest{
Username: username,
Password: password,
}
response := new(model.LoginResult)
_, _, err := c.doRequestNoAuth(ctx, "POST", &url, clientlib.SendJSON(request), clientlib.RecvJSON(response), loginErrorCheckOpt)
return response, err
}
// loginErrorCheckOpt works similarly to `clientlib.DefaultErrorCheck`, except it parses the error response
func loginErrorCheckOpt(r *clientlib.Request) {
r.ErrorCheck = func(r *clientlib.Request, doErr error, res *http.Response) error {
if doErr != nil {
return errors.Wrap(doErr, r.ErrorFields())
}
status := res.StatusCode
if status >= 200 && status < 300 {
return nil
}
defer res.Body.Close()
lError := new(model.LoginError)
message := fmt.Sprintf("%s %s returned %d", r.Method, r.URL.String(), status)
lError.HTTPError = errors.NewHTTPError(status, message).
With(r.ErrorFields())
var rawLoginErr model.RawLoginError
err := json.NewDecoder(res.Body).Decode(&rawLoginErr)
if err == nil {
lError.Raw = &rawLoginErr
}
return lError
}
}