diff --git a/api/client.go b/api/client.go index f5c94f5c7..78859a14f 100644 --- a/api/client.go +++ b/api/client.go @@ -124,8 +124,8 @@ type RevocationRequest struct { type RevocationResponse struct { // RevokedCerts is an array of certificates that were revoked RevokedCerts []RevokedCert - // CRL is base64 encoded DER bytes of a CRL that contains all unexpired revoked certificates - CRL string + // CRL is PEM-encoded certificate revocation list (CRL) that contains all unexpired revoked certificates + CRL []byte } // RevokedCert represents a revoked certificate @@ -189,7 +189,8 @@ type GenCRLRequest struct { // GenCRLResponse represents a response to get CRL type GenCRLResponse struct { - CRL string + // CRL is PEM-encoded certificate revocation list (CRL) that contains requested unexpired revoked certificates + CRL []byte } // AddIdentityRequest represents the request to add a new identity to the diff --git a/cmd/fabric-ca-client/gencrl.go b/cmd/fabric-ca-client/gencrl.go index e7edbc425..56f53ecfa 100644 --- a/cmd/fabric-ca-client/gencrl.go +++ b/cmd/fabric-ca-client/gencrl.go @@ -13,7 +13,6 @@ limitations under the License. package main import ( - "fmt" "os" "path" "path/filepath" @@ -32,10 +31,6 @@ const ( crlsFolder = "crls" // crlFile is the name of the file used to the generate CRL crlFile = "crl.pem" - // crlPemHeader is the header of a X509 CRL - crlPemHeader = "-----BEGIN X509 CRL-----\n" - // crlPemFooter is the footer of a X509 CRL - crlPemFooter = "\n-----END X509 CRL-----\n" ) func (c *ClientCmd) newGenCRLCommand() *cobra.Command { @@ -134,7 +129,7 @@ func (c *ClientCmd) runGenCRL() error { } // Store the CRL -func storeCRL(config *lib.ClientConfig, crl string) error { +func storeCRL(config *lib.ClientConfig, crl []byte) error { dirName := path.Join(config.MSPDir, crlsFolder) if _, err := os.Stat(dirName); os.IsNotExist(err) { mkdirErr := os.MkdirAll(dirName, os.ModeDir|0755) @@ -143,7 +138,7 @@ func storeCRL(config *lib.ClientConfig, crl string) error { } } fileName := path.Join(dirName, crlFile) - err := util.WriteFile(fileName, []byte(fmt.Sprintf("%s%s%s", crlPemHeader, crl, crlPemFooter)), 0644) + err := util.WriteFile(fileName, crl, 0644) if err != nil { return errors.Wrapf(err, "Failed to write CRL to the file %s", fileName) } diff --git a/cmd/fabric-ca-client/revoke.go b/cmd/fabric-ca-client/revoke.go index b99b96093..7ff2dd54a 100644 --- a/cmd/fabric-ca-client/revoke.go +++ b/cmd/fabric-ca-client/revoke.go @@ -106,7 +106,7 @@ func (c *ClientCmd) runRevoke(cmd *cobra.Command) error { } log.Infof("Sucessfully revoked certificates: %+v", result.RevokedCerts) - if req.GenCRL && result.CRL != "" { + if req.GenCRL { return storeCRL(c.clientCfg, result.CRL) } return nil diff --git a/lib/identity.go b/lib/identity.go index 6bac67db7..947310194 100644 --- a/lib/identity.go +++ b/lib/identity.go @@ -165,13 +165,17 @@ func (i *Identity) Revoke(req *api.RevocationRequest) (*api.RevocationResponse, if err != nil { return nil, err } - var result api.RevocationResponse + var result revocationResponseNet err = i.Post("revoke", reqBody, &result) if err != nil { return nil, err } log.Debugf("Successfully revoked certificates: %+v", req) - return &result, nil + crl, err := util.B64Decode(result.CRL) + if err != nil { + return nil, err + } + return &api.RevocationResponse{RevokedCerts: result.RevokedCerts, CRL: crl}, nil } // RevokeSelf revokes the current identity and all certificates @@ -191,13 +195,17 @@ func (i *Identity) GenCRL(req *api.GenCRLRequest) (*api.GenCRLResponse, error) { if err != nil { return nil, err } - var result api.GenCRLResponse + var result genCRLResponseNet err = i.Post("gencrl", reqBody, &result) if err != nil { return nil, err } log.Debugf("Successfully generated CRL: %+v", req) - return &result, nil + crl, err := util.B64Decode(result.CRL) + if err != nil { + return nil, err + } + return &api.GenCRLResponse{CRL: crl}, nil } // GetIdentity returns information about the requested identity diff --git a/lib/servergencrl.go b/lib/servergencrl.go index f97bd2080..cd2820739 100644 --- a/lib/servergencrl.go +++ b/lib/servergencrl.go @@ -19,6 +19,7 @@ package lib import ( "crypto/x509" "crypto/x509/pkix" + "encoding/pem" "fmt" "io/ioutil" "math/big" @@ -31,9 +32,14 @@ import ( "github.com/pkg/errors" ) -// The response to the GET /info request +const ( + crlPemType = "X509 CRL" +) + +// The response to the POST /gencrl request type genCRLResponseNet struct { - CRL []byte + // Base64 encoding of PEM-encoded CRL + CRL string } func newGenCRLEndpoint(s *Server) *serverEndpoint { @@ -80,7 +86,7 @@ func genCRLHandler(ctx *serverRequestContext) (interface{}, error) { } log.Debugf("Successfully generated CRL") - resp := &genCRLResponseNet{CRL: crl} + resp := &genCRLResponseNet{CRL: util.B64Encode(crl)} return resp, nil } @@ -92,32 +98,38 @@ func genCRL(ca *CA, req api.GenCRLRequest) ([]byte, error) { revokedBefore = time.Now().UTC() } if req.RevokedAfter.After(revokedBefore) { - return nil, newHTTPErr(400, ErrInvalidRevokedAfter, "Invalid 'revokedafter' value. It must not be a timestamp greater than 'revokedbefore'") + return nil, newHTTPErr(400, ErrInvalidRevokedAfter, + "Invalid 'revokedafter' value. It must not be a timestamp greater than 'revokedbefore'") } if req.ExpireAfter.After(req.ExpireBefore) { - return nil, newHTTPErr(400, ErrInvalidExpiredAfter, "Invalid 'expireafter' value. It must not be a timestamp greater than 'expirebefore'") + return nil, newHTTPErr(400, ErrInvalidExpiredAfter, + "Invalid 'expireafter' value. It must not be a timestamp greater than 'expirebefore'") } // Get revoked certificates from the database certs, err := ca.certDBAccessor.GetRevokedCertificates(req.ExpireAfter, req.ExpireBefore, req.RevokedAfter, revokedBefore) if err != nil { - return nil, newHTTPErr(500, ErrRevokedCertsFromDB, "Failed to get revoked certificates from the database: %s", err) + return nil, newHTTPErr(500, ErrRevokedCertsFromDB, + "Failed to get revoked certificates from the database: %s", err) } caCert, err := getCACert(ca) if err != nil { - return nil, newHTTPErr(500, ErrGetCACert, "Failed to get certficate for the CA '%s': %s", ca.HomeDir, err) + return nil, newHTTPErr(500, ErrGetCACert, + "Failed to get certficate for the CA '%s': %s", ca.HomeDir, err) } if !canSignCRL(caCert) { - return nil, newHTTPErr(500, ErrNoCrlSignAuth, "The CA does not have authority to generate a CRL. Its certificate does not have 'crl sign' key usage") + return nil, newHTTPErr(500, ErrNoCrlSignAuth, + "The CA does not have authority to generate a CRL. Its certificate does not have 'crl sign' key usage") } // Get the signer for the CA _, signer, err := util.GetSignerFromCert(caCert, ca.csp) if err != nil { - return nil, newHTTPErr(500, ErrGetCASigner, "Failed to get signer for the CA '%s': %s", ca.HomeDir, err) + return nil, newHTTPErr(500, ErrGetCASigner, + "Failed to get signer for the CA '%s': %s", ca.HomeDir, err) } expiry := time.Now().UTC().Add(ca.Config.CRL.Expiry) @@ -138,7 +150,8 @@ func genCRL(ca *CA, req api.GenCRLRequest) ([]byte, error) { if err != nil { return nil, newHTTPErr(500, ErrGenCRL, "Failed to generate the CRL for the CA '%s': %s", ca.HomeDir, err) } - return crl, nil + blk := &pem.Block{Bytes: crl, Type: crlPemType} + return pem.EncodeToMemory(blk), nil } func getCACert(ca *CA) (*x509.Certificate, error) { diff --git a/lib/serverrevoke.go b/lib/serverrevoke.go index cf2a7eef7..5202050a9 100644 --- a/lib/serverrevoke.go +++ b/lib/serverrevoke.go @@ -28,7 +28,7 @@ import ( type revocationResponseNet struct { RevokedCerts []api.RevokedCert - CRL []byte + CRL string } func newRevokeEndpoint(s *Server) *serverEndpoint { @@ -158,8 +158,7 @@ func revokeHandler(ctx *serverRequestContext) (interface{}, error) { if err != nil { return nil, err } - result.CRL = crl + result.CRL = util.B64Encode(crl) } - return result, nil } diff --git a/swagger/swagger-fabric-ca.json b/swagger/swagger-fabric-ca.json index 179c127a0..b22690f4d 100644 --- a/swagger/swagger-fabric-ca.json +++ b/swagger/swagger-fabric-ca.json @@ -125,7 +125,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An HTTP basic authorization header where: \n* *user* is the enrollment ID; \n* *password* is the enrollment secret.", @@ -277,7 +278,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -429,7 +431,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -598,7 +601,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -670,8 +674,31 @@ "description": "Boolean indicating if the request was successful." }, "Result": { - "type": "string", - "description": "Message related to revocation" + "type": "object", + "properties": { + "revokedcerts": { + "type": "array", + "description": "An array of revoked certificates", + "items": { + "type": "object", + "description": "A revoked certificate", + "properties": { + "serial": { + "type": "string", + "description": "Serial number of the revoked certificate" + }, + "aki": { + "type": "string", + "description": "Authority Key Identifier (AKI) of the revoked certificate" + } + } + } + }, + "crl": { + "type": "string", + "description": "base64 encoded PEM-encoded CRL" + } + } }, "Errors": { "type": "array", @@ -739,7 +766,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -808,7 +836,7 @@ "properties": { "crl": { "type": "string", - "description": "base64 encoded DER bytes of the CRL" + "description": "base64 encoded PEM-encoded CRL" } }, "required": [ @@ -881,7 +909,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -922,7 +951,10 @@ "description": "Name of the CA containing these affiliations." } }, - "required": [ "affiliations", "caname" ] + "required": [ + "affiliations", + "caname" + ] }, "Errors": { "type": "array", @@ -988,18 +1020,21 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", "required": true, "type": "string" - }, { + }, + { "name": "force", "in": "query", "description": "If any of the parent affiliations do not exist and **force** is true, create all parent affiliations also", "type": "boolean" - }, { + }, + { "name": "body", "in": "body", "description": "The request body", @@ -1012,7 +1047,10 @@ "description": "The affiliation path to create." }, "caname": { - "type": [ "string", "null" ], + "type": [ + "string", + "null" + ], "description": "Name of the CA to send the request to within the Fabric CA server." } }, @@ -1044,7 +1082,10 @@ "description": "Name of the CA containing this affiliation." } }, - "required": [ "affiliation_path", "caname" ] + "required": [ + "affiliation_path", + "caname" + ] }, "Errors": { "type": "array", @@ -1112,13 +1153,15 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "affiliation", "in": "path", "description": "An affiliation path", "required": true, "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -1217,24 +1260,28 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "affiliation", "in": "path", "description": "An affiliation path", "required": true, "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", "required": true, "type": "string" - }, { + }, + { "name": "force", "in": "query", "description": "If any identities are associated with this affiliation, force causes these identities' affiliations to be renamed; otherwise, an error is returned", "type": "boolean" - }, { + }, + { "name": "body", "in": "body", "description": "The request body", @@ -1247,11 +1294,16 @@ "description": "The new affiliation path." }, "caname": { - "type": [ "string", "null" ], + "type": [ + "string", + "null" + ], "description": "Name of the CA to send the request to within the Fabric CA server." } }, - "required": ["affiliation_path"] + "required": [ + "affiliation_path" + ] } } ], @@ -1277,7 +1329,10 @@ "description": "Name of the CA containing this affiliation." } }, - "required": [ "affiliation_path", "caname" ] + "required": [ + "affiliation_path", + "caname" + ] }, "Errors": { "type": "array", @@ -1343,19 +1398,22 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "affiliation", "in": "path", "description": "An affiliation path", "required": true, "type": "string" - }, { + }, + { "name": "force", "in": "query", "description": "If there are any child affiliations or any identities are associated with this affiliation or child affiliations, force causes these identities and child affiliations to be deleted; otherwise, an error is returned", "required": false, "type": "boolean" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -1444,11 +1502,15 @@ } }, "caname": { - "type": "string", - "description": "Name of the CA containing these identities and affiliations." + "type": "string", + "description": "Name of the CA containing these identities and affiliations." } }, - "required": [ "affiliations", "identities", "caname" ] + "required": [ + "affiliations", + "identities", + "caname" + ] }, "Errors": { "type": "array", @@ -1516,7 +1578,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -1590,11 +1653,14 @@ } }, "caname": { - "type": "string", - "description": "Name of the CA containing these identities." + "type": "string", + "description": "Name of the CA containing these identities." } }, - "required": [ "identities", "caname" ] + "required": [ + "identities", + "caname" + ] }, "Errors": { "type": "array", @@ -1660,7 +1726,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -1807,8 +1874,8 @@ } }, "caname": { - "type": "string", - "description": "Name of the CA containing this identity." + "type": "string", + "description": "Name of the CA containing this identity." } } }, @@ -1878,13 +1945,15 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "id", "in": "path", "description": "An enrollment ID", "required": true, "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -1950,8 +2019,8 @@ } }, "caname": { - "type": "string", - "description": "Name of the CA containing this identity." + "type": "string", + "description": "Name of the CA containing this identity." } } }, @@ -2019,13 +2088,15 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "id", "in": "path", "description": "An enrollment ID", "required": true, "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -2165,8 +2236,8 @@ } }, "caname": { - "type": "string", - "description": "Name of the CA containing this identity." + "type": "string", + "description": "Name of the CA containing this identity." } } }, @@ -2234,13 +2305,15 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "id", "in": "path", "description": "An enrollment ID", "required": true, "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -2310,8 +2383,8 @@ } }, "caname": { - "type": "string", - "description": "Name of the CA containing the deleted identity." + "type": "string", + "description": "Name of the CA containing the deleted identity." } }, "required": [ @@ -2385,7 +2458,8 @@ "in": "query", "description": "The name of the CA to direct this request to within the server, or the default CA if not specified", "type": "string" - }, { + }, + { "name": "Authorization", "in": "header", "description": "An enrollment token consisting of two base 64 encoded parts separated by a period: \n* an enrollment certificate; \n* a signature over the certificate and body of request.", @@ -2563,4 +2637,4 @@ } } } -} +} \ No newline at end of file