| /* |
| * Copyright (C) 2009 Martin Willi |
| * HSR Hochschule fuer Technik Rapperswil |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| #include "pgp_cert.h" |
| #include "pgp_utils.h" |
| |
| #include <time.h> |
| |
| #include <utils/debug.h> |
| |
| typedef struct private_pgp_cert_t private_pgp_cert_t; |
| |
| /** |
| * Private data of an pgp_cert_t object. |
| */ |
| struct private_pgp_cert_t { |
| |
| /** |
| * Implements pgp_cert_t interface. |
| */ |
| pgp_cert_t public; |
| |
| /** |
| * Public key of the certificate |
| */ |
| public_key_t *key; |
| |
| /** |
| * version of the public key |
| */ |
| uint32_t version; |
| |
| /** |
| * creation time |
| */ |
| uint32_t created; |
| |
| /** |
| * days the certificate is valid |
| */ |
| uint32_t valid; |
| |
| /** |
| * userid of the certificate |
| */ |
| identification_t *user_id; |
| |
| /** |
| * v3 or v4 fingerprint of the PGP public key |
| */ |
| chunk_t fingerprint; |
| |
| /** |
| * full PGP encoding |
| */ |
| chunk_t encoding; |
| |
| /** |
| * reference counter |
| */ |
| refcount_t ref; |
| }; |
| |
| |
| METHOD(certificate_t, get_type, certificate_type_t, |
| private_pgp_cert_t *this) |
| { |
| return CERT_GPG; |
| } |
| |
| METHOD(certificate_t, get_subject,identification_t*, |
| private_pgp_cert_t *this) |
| { |
| return this->user_id; |
| } |
| |
| METHOD(certificate_t, get_issuer, identification_t*, |
| private_pgp_cert_t *this) |
| { |
| return this->user_id; |
| } |
| |
| METHOD(certificate_t, has_subject, id_match_t, |
| private_pgp_cert_t *this, identification_t *subject) |
| { |
| id_match_t match_user_id; |
| |
| match_user_id = this->user_id->matches(this->user_id, subject); |
| if (match_user_id == ID_MATCH_NONE && |
| subject->get_type(subject) == ID_KEY_ID && |
| chunk_equals(this->fingerprint, subject->get_encoding(subject))) |
| { |
| return ID_MATCH_PERFECT; |
| } |
| return match_user_id; |
| } |
| |
| METHOD(certificate_t, has_issuer, id_match_t, |
| private_pgp_cert_t *this, identification_t *issuer) |
| { |
| return ID_MATCH_NONE; |
| } |
| |
| METHOD(certificate_t, issued_by,bool, |
| private_pgp_cert_t *this, certificate_t *issuer, signature_params_t **scheme) |
| { |
| /* TODO: check signature blobs for a valid signature */ |
| return FALSE; |
| } |
| |
| METHOD(certificate_t, get_public_key, public_key_t*, |
| private_pgp_cert_t *this) |
| { |
| this->key->get_ref(this->key); |
| return this->key; |
| } |
| |
| METHOD(certificate_t, get_ref, certificate_t*, |
| private_pgp_cert_t *this) |
| { |
| ref_get(&this->ref); |
| return &this->public.interface.interface; |
| } |
| |
| METHOD(certificate_t, get_validity, bool, |
| private_pgp_cert_t *this, time_t *when, time_t *not_before, |
| time_t *not_after) |
| { |
| time_t t, until; |
| |
| if (when) |
| { |
| t = *when; |
| } |
| else |
| { |
| t = time(NULL); |
| } |
| if (not_before) |
| { |
| *not_before = this->created; |
| } |
| if (this->valid) |
| { |
| until = this->valid + this->created * 24 * 60 * 60; |
| } |
| else |
| { |
| /* Jan 19 03:14:07 UTC 2038 */ |
| until = TIME_32_BIT_SIGNED_MAX; |
| } |
| if (not_after) |
| { |
| *not_after = until; |
| } |
| return (t >= this->valid && t <= until); |
| } |
| |
| METHOD(certificate_t, get_encoding, bool, |
| private_pgp_cert_t *this, cred_encoding_type_t type, chunk_t *encoding) |
| { |
| if (type == CERT_PGP_PKT) |
| { |
| *encoding = chunk_clone(this->encoding); |
| return TRUE; |
| } |
| return lib->encoding->encode(lib->encoding, type, NULL, encoding, |
| CRED_PART_PGP_CERT, this->encoding, CRED_PART_END); |
| } |
| |
| METHOD(certificate_t, equals, bool, |
| private_pgp_cert_t *this, certificate_t *other) |
| { |
| chunk_t encoding; |
| bool equal; |
| |
| if (this == (private_pgp_cert_t*)other) |
| { |
| return TRUE; |
| } |
| if (other->get_type(other) != CERT_X509) |
| { |
| return FALSE; |
| } |
| if (other->equals == (void*)equals) |
| { /* skip allocation if we have the same implementation */ |
| return chunk_equals(this->encoding, ((private_pgp_cert_t*)other)->encoding); |
| } |
| if (!other->get_encoding(other, CERT_PGP_PKT, &encoding)) |
| { |
| return FALSE; |
| } |
| equal = chunk_equals(this->encoding, encoding); |
| free(encoding.ptr); |
| return equal; |
| } |
| |
| METHOD(certificate_t, destroy, void, |
| private_pgp_cert_t *this) |
| { |
| if (ref_put(&this->ref)) |
| { |
| DESTROY_IF(this->key); |
| DESTROY_IF(this->user_id); |
| free(this->fingerprint.ptr); |
| free(this->encoding.ptr); |
| free(this); |
| } |
| } |
| |
| METHOD(pgp_certificate_t, get_fingerprint, chunk_t, |
| private_pgp_cert_t *this) |
| { |
| return this->fingerprint; |
| } |
| |
| /** |
| * See header |
| */ |
| private_pgp_cert_t *create_empty() |
| { |
| private_pgp_cert_t *this; |
| |
| INIT(this, |
| .public = { |
| .interface = { |
| .interface = { |
| .get_type = _get_type, |
| .get_subject = _get_subject, |
| .get_issuer = _get_issuer, |
| .has_subject = _has_subject, |
| .has_issuer = _has_issuer, |
| .issued_by = _issued_by, |
| .get_public_key = _get_public_key, |
| .get_validity = _get_validity, |
| .get_encoding = _get_encoding, |
| .equals = _equals, |
| .get_ref = _get_ref, |
| .destroy = _destroy, |
| }, |
| .get_fingerprint = _get_fingerprint, |
| }, |
| }, |
| .ref = 1, |
| ); |
| |
| return this; |
| } |
| |
| /** |
| * Parse the public key packet of a PGP certificate |
| */ |
| static bool parse_public_key(private_pgp_cert_t *this, chunk_t packet) |
| { |
| chunk_t pubkey_packet = packet; |
| |
| if (!pgp_read_scalar(&packet, 1, &this->version)) |
| { |
| return FALSE; |
| } |
| switch (this->version) |
| { |
| case 3: |
| if (!pgp_read_scalar(&packet, 4, &this->created) || |
| !pgp_read_scalar(&packet, 2, &this->valid)) |
| { |
| return FALSE; |
| } |
| break; |
| case 4: |
| if (!pgp_read_scalar(&packet, 4, &this->created)) |
| { |
| return FALSE; |
| } |
| break; |
| default: |
| DBG1(DBG_ASN, "PGP packet version V%d not supported", |
| this->version); |
| return FALSE; |
| } |
| if (this->valid) |
| { |
| DBG2(DBG_ASN, "L2 - created %T, valid %d days", &this->created, FALSE, |
| this->valid); |
| } |
| else |
| { |
| DBG2(DBG_ASN, "L2 - created %T, never expires", &this->created, FALSE); |
| } |
| DESTROY_IF(this->key); |
| this->key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_ANY, |
| BUILD_BLOB_PGP, packet, BUILD_END); |
| if (this->key == NULL) |
| { |
| return FALSE; |
| } |
| |
| /* compute V4 or V3 fingerprint according to section 12.2 of RFC 4880 */ |
| if (this->version == 4) |
| { |
| chunk_t pubkey_packet_header = chunk_from_chars( |
| 0x99, pubkey_packet.len / 256, pubkey_packet.len % 256 |
| ); |
| hasher_t *hasher; |
| |
| hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); |
| if (hasher == NULL) |
| { |
| DBG1(DBG_ASN, "no SHA-1 hasher available"); |
| return FALSE; |
| } |
| if (!hasher->allocate_hash(hasher, pubkey_packet_header, NULL) || |
| !hasher->allocate_hash(hasher, pubkey_packet, &this->fingerprint)) |
| { |
| hasher->destroy(hasher); |
| return FALSE; |
| } |
| hasher->destroy(hasher); |
| DBG2(DBG_ASN, "L2 - v4 fingerprint %#B", &this->fingerprint); |
| } |
| else |
| { |
| /* V3 fingerprint is computed by public_key_t class */ |
| if (!this->key->get_fingerprint(this->key, KEYID_PGPV3, |
| &this->fingerprint)) |
| { |
| return FALSE; |
| } |
| this->fingerprint = chunk_clone(this->fingerprint); |
| DBG2(DBG_ASN, "L2 - v3 fingerprint %#B", &this->fingerprint); |
| } |
| return TRUE; |
| } |
| |
| /** |
| * Parse the signature packet of a PGP certificate |
| */ |
| static bool parse_signature(private_pgp_cert_t *this, chunk_t packet) |
| { |
| uint32_t version, len, type, created; |
| |
| if (!pgp_read_scalar(&packet, 1, &version)) |
| { |
| return FALSE; |
| } |
| |
| /* we parse only v3 or v4 signature packets */ |
| if (version != 3 && version != 4) |
| { |
| DBG2(DBG_ASN, "L2 - v%d signature ignored", version); |
| return TRUE; |
| } |
| if (version == 4) |
| { |
| if (!pgp_read_scalar(&packet, 1, &type)) |
| { |
| return FALSE; |
| } |
| DBG2(DBG_ASN, "L2 - v%d signature of type 0x%02x", version, type); |
| } |
| else |
| { |
| if (!pgp_read_scalar(&packet, 1, &len) || len != 5) |
| { |
| return FALSE; |
| } |
| if (!pgp_read_scalar(&packet, 1, &type) || |
| !pgp_read_scalar(&packet, 4, &created)) |
| { |
| return FALSE; |
| } |
| DBG2(DBG_ASN, "L2 - v3 signature of type 0x%02x, created %T", type, |
| &created, FALSE); |
| } |
| /* TODO: parse and save signature to a list */ |
| return TRUE; |
| } |
| |
| /** |
| * Parse the userid packet of a PGP certificate |
| */ |
| static bool parse_user_id(private_pgp_cert_t *this, chunk_t packet) |
| { |
| DESTROY_IF(this->user_id); |
| this->user_id = identification_create_from_encoding(ID_KEY_ID, packet); |
| DBG2(DBG_ASN, "L2 - '%Y'", this->user_id); |
| return TRUE; |
| } |
| |
| /** |
| * See header. |
| */ |
| pgp_cert_t *pgp_cert_load(certificate_type_t type, va_list args) |
| { |
| chunk_t packet, blob = chunk_empty; |
| pgp_packet_tag_t tag; |
| private_pgp_cert_t *this; |
| |
| while (TRUE) |
| { |
| switch (va_arg(args, builder_part_t)) |
| { |
| case BUILD_BLOB_PGP: |
| blob = va_arg(args, chunk_t); |
| continue; |
| case BUILD_END: |
| break; |
| default: |
| return NULL; |
| } |
| break; |
| } |
| |
| this = create_empty(); |
| this->encoding = chunk_clone(blob); |
| while (blob.len) |
| { |
| if (!pgp_read_packet(&blob, &packet, &tag)) |
| { |
| destroy(this); |
| return NULL; |
| } |
| switch (tag) |
| { |
| case PGP_PKT_PUBLIC_KEY: |
| if (!parse_public_key(this, packet)) |
| { |
| destroy(this); |
| return NULL; |
| } |
| break; |
| case PGP_PKT_SIGNATURE: |
| if (!parse_signature(this, packet)) |
| { |
| destroy(this); |
| return NULL; |
| } |
| break; |
| case PGP_PKT_USER_ID: |
| if (!parse_user_id(this, packet)) |
| { |
| destroy(this); |
| return NULL; |
| } |
| break; |
| default: |
| DBG1(DBG_LIB, "ignoring %N packet in PGP certificate", |
| pgp_packet_tag_names, tag); |
| break; |
| } |
| } |
| if (this->key) |
| { |
| return &this->public; |
| } |
| destroy(this); |
| return NULL; |
| } |
| |